Skip to content

Commit 206b8a5

Browse files
authored
refactor(amazonq): Improve robustness of lsp installation process (#6364)
## Problem - resolvers should produce runable binaries with the correct permissions - nodepaths should correctly target the correct os - workspace context lsp should reference the correct location ## Solution - resolvers now produce runable binaries - nodepaths are now correct for the workspace context lsp on windows/mac/linux - lsp location is now correct for the workspace context lsp --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c110675 commit 206b8a5

File tree

9 files changed

+73
-23
lines changed

9 files changed

+73
-23
lines changed

packages/amazonq/src/lsp/activation.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@ import vscode from 'vscode'
77
import { startLanguageServer } from './client'
88
import { AmazonQLSPResolver } from './lspInstaller'
99
import { ToolkitError } from 'aws-core-vscode/shared'
10-
import path from 'path'
1110

1211
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
1312
try {
1413
const installResult = await new AmazonQLSPResolver().resolve()
15-
const serverLocation =
16-
installResult.location === 'override'
17-
? installResult.assetDirectory
18-
: path.join(installResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js')
19-
await startLanguageServer(ctx, serverLocation)
14+
await startLanguageServer(ctx, installResult.resourcePaths)
2015
} catch (err) {
2116
const e = err as ToolkitError
2217
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)

packages/amazonq/src/lsp/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import { registerInlineCompletion } from '../inline/completion'
1212
import { AmazonQLspAuth, notificationTypes, writeEncryptionInit } from './auth'
1313
import { AuthUtil } from 'aws-core-vscode/codewhisperer'
1414
import { ConnectionMetadata } from '@aws/language-server-runtimes/protocol'
15+
import { ResourcePaths } from 'aws-core-vscode/shared'
1516

1617
const localize = nls.loadMessageBundle()
1718

18-
export function startLanguageServer(extensionContext: vscode.ExtensionContext, serverPath: string) {
19+
export function startLanguageServer(extensionContext: vscode.ExtensionContext, resourcePaths: ResourcePaths) {
1920
const toDispose = extensionContext.subscriptions
2021

2122
// The debug options for the server
@@ -30,14 +31,16 @@ export function startLanguageServer(extensionContext: vscode.ExtensionContext, s
3031
],
3132
}
3233

34+
const serverPath = resourcePaths.lsp
35+
3336
// If the extension is launch in debug mode the debug server options are use
3437
// Otherwise the run options are used
3538
let serverOptions: ServerOptions = {
3639
run: { module: serverPath, transport: TransportKind.ipc },
3740
debug: { module: serverPath, transport: TransportKind.ipc, options: debugOptions },
3841
}
3942

40-
const child = cp.spawn('node', [serverPath, ...debugOptions.execArgv])
43+
const child = cp.spawn(resourcePaths.node, [serverPath, ...debugOptions.execArgv])
4144
writeEncryptionInit(child.stdin)
4245

4346
serverOptions = () => Promise.resolve(child)

packages/amazonq/src/lsp/lspInstaller.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,32 @@
55

66
import * as vscode from 'vscode'
77
import { Range } from 'semver'
8-
import { ManifestResolver, LanguageServerResolver, LspResolver, LspResult } from 'aws-core-vscode/shared'
8+
import {
9+
ManifestResolver,
10+
LanguageServerResolver,
11+
LspResolver,
12+
fs,
13+
LspResolution,
14+
getNodeExecutableName,
15+
} from 'aws-core-vscode/shared'
16+
import path from 'path'
917

1018
const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json'
1119
export const supportedLspServerVersions = '^2.3.0'
1220

1321
export class AmazonQLSPResolver implements LspResolver {
14-
async resolve(): Promise<LspResult> {
22+
async resolve(): Promise<LspResolution> {
1523
const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE
1624
if (overrideLocation) {
1725
void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`)
1826
return {
1927
assetDirectory: overrideLocation,
2028
location: 'override',
2129
version: '0.0.0',
30+
resourcePaths: {
31+
lsp: overrideLocation,
32+
node: getNodeExecutableName(),
33+
},
2234
}
2335
}
2436

@@ -31,7 +43,16 @@ export class AmazonQLSPResolver implements LspResolver {
3143
new Range(supportedLspServerVersions)
3244
).resolve()
3345

46+
const nodePath = path.join(installationResult.assetDirectory, `servers/${getNodeExecutableName()}`)
47+
await fs.chmod(nodePath, 0o755)
48+
3449
// TODO Cleanup old versions of language servers
35-
return installationResult
50+
return {
51+
...installationResult,
52+
resourcePaths: {
53+
lsp: path.join(installationResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js'),
54+
node: nodePath,
55+
},
56+
}
3657
}
3758
}

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
} from './types'
3333
import { Writable } from 'stream'
3434
import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings'
35-
import { fs, getLogger, globals } from '../../shared'
35+
import { ResourcePaths, fs, getLogger, globals } from '../../shared'
3636

3737
const localize = nls.loadMessageBundle()
3838

@@ -172,7 +172,7 @@ export class LspClient {
172172
* It will create a output channel named Amazon Q Language Server.
173173
* This function assumes the LSP server has already been downloaded.
174174
*/
175-
export async function activate(extensionContext: ExtensionContext, serverModule: string) {
175+
export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) {
176176
LspClient.instance
177177
const toDispose = extensionContext.subscriptions
178178

@@ -195,12 +195,9 @@ export async function activate(extensionContext: ExtensionContext, serverModule:
195195
delete process.env.Q_WORKER_THREADS
196196
}
197197

198-
const nodename = process.platform === 'win32' ? 'node.exe' : 'node'
198+
const serverModule = resourcePaths.lsp
199199

200-
const child = spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [
201-
serverModule,
202-
...debugOptions.execArgv,
203-
])
200+
const child = spawn(resourcePaths.node, [serverModule, ...debugOptions.execArgv])
204201
// share an encryption key using stdin
205202
// follow same practice of DEXP LSP server
206203
writeEncryptionInit(child.stdin)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export class LspController {
161161
setImmediate(async () => {
162162
try {
163163
const installResult = await new WorkspaceLSPResolver().resolve()
164-
await activateLsp(context, path.join(installResult.assetDirectory, 'resources/qserver/lspServer.js'))
164+
await activateLsp(context, installResult.resourcePaths)
165165
getLogger().info('LspController: LSP activated')
166166
void LspController.instance.buildIndex(buildIndexConfig)
167167
// log the LSP server CPU and Memory usage per 30 minutes.

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { LspResolver, LspResult } from '../../shared/languageServer/types'
6+
import path from 'path'
7+
import { LspResolution, LspResolver } from '../../shared/languageServer/types'
78
import { ManifestResolver } from '../../shared/languageServer/manifestResolver'
89
import { LanguageServerResolver } from '../../shared/languageServer/lspResolver'
910
import { Range } from 'semver'
11+
import { getNodeExecutableName } from '../../shared/languageServer/utils/platform'
12+
import { fs } from '../../shared/fs/fs'
1013

1114
const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json'
1215
// this LSP client in Q extension is only going to work with these LSP server versions
1316
const supportedLspServerVersions = '0.1.32'
1417

1518
export class WorkspaceLSPResolver implements LspResolver {
16-
async resolve(): Promise<LspResult> {
19+
async resolve(): Promise<LspResolution> {
1720
const name = 'AmazonQ-Workspace'
1821
const manifest = await new ManifestResolver(manifestUrl, name).resolve()
1922
const installationResult = await new LanguageServerResolver(
@@ -22,7 +25,21 @@ export class WorkspaceLSPResolver implements LspResolver {
2225
new Range(supportedLspServerVersions)
2326
).resolve()
2427

28+
const nodeName =
29+
process.platform === 'win32' ? getNodeExecutableName() : `node-${process.platform}-${process.arch}`
30+
const nodePath = path.join(installationResult.assetDirectory, nodeName)
31+
await fs.chmod(nodePath, 0o755)
32+
2533
// TODO Cleanup old versions of language servers
26-
return installationResult
34+
return {
35+
...installationResult,
36+
resourcePaths: {
37+
lsp: path.join(
38+
installationResult.assetDirectory,
39+
`qserver-${process.platform}-${process.arch}/qserver/lspServer.js`
40+
),
41+
node: nodePath,
42+
},
43+
}
2744
}
2845
}

packages/core/src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ export * from './languageServer/manifestResolver'
6363
export * from './languageServer/lspResolver'
6464
export * from './languageServer/types'
6565
export { default as request } from './request'
66+
export * from './languageServer/utils/platform'

packages/core/src/shared/languageServer/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@ export interface LspResult {
1515
assetDirectory: string
1616
}
1717

18+
export interface ResourcePaths {
19+
lsp: string
20+
node: string
21+
}
22+
export interface LspResolution extends LspResult {
23+
resourcePaths: ResourcePaths
24+
}
25+
1826
export interface LspResolver {
19-
resolve(): Promise<LspResult>
27+
resolve(): Promise<LspResolution>
2028
}
2129

2230
export interface TargetContent {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export function getNodeExecutableName(): string {
7+
return process.platform === 'win32' ? 'node.exe' : 'node'
8+
}

0 commit comments

Comments
 (0)