Skip to content

Commit 20efa94

Browse files
authored
feat(amazonq): Auto update language servers when new versions are available (#6310)
## Problem: - Only one version of a language server is installed right now ## Solution: - Automatically update when a new version is available in the manifest --- - 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 86a6e6e commit 20efa94

File tree

18 files changed

+671
-597
lines changed

18 files changed

+671
-597
lines changed

packages/amazonq/src/lsp/activation.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44
*/
55

66
import vscode from 'vscode'
7-
import path from 'path'
8-
import { AmazonQLSPDownloader } from './download'
97
import { startLanguageServer } from './client'
8+
import { AmazonQLSPResolver } from './lspInstaller'
9+
import { ToolkitError } from 'aws-core-vscode/shared'
10+
import path from 'path'
1011

1112
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
12-
const serverPath = ctx.asAbsolutePath('resources/qdeveloperserver')
13-
const clientPath = ctx.asAbsolutePath('resources/qdeveloperclient')
14-
await new AmazonQLSPDownloader(serverPath, clientPath).tryInstallLsp()
15-
await startLanguageServer(ctx, path.join(serverPath, 'aws-lsp-codewhisperer.js'))
13+
try {
14+
const installResult = await new AmazonQLSPResolver().resolve()
15+
await startLanguageServer(ctx, path.join(installResult.assetDirectory, 'servers/aws-lsp-codewhisperer.js'))
16+
} catch (err) {
17+
const e = err as ToolkitError
18+
void vscode.window.showInformationMessage(`Unable to launch amazonq language server: ${e.message}`)
19+
}
1620
}

packages/amazonq/src/lsp/download.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import { Range } from 'semver'
8+
import { ManifestResolver, LanguageServerResolver, LspResolver, LspResult } from 'aws-core-vscode/shared'
9+
10+
const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json'
11+
const supportedLspServerVersions = '^2.3.1'
12+
13+
export class AmazonQLSPResolver implements LspResolver {
14+
async resolve(): Promise<LspResult> {
15+
const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE
16+
if (overrideLocation) {
17+
void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`)
18+
return {
19+
assetDirectory: overrideLocation,
20+
location: 'override',
21+
version: '0.0.0',
22+
}
23+
}
24+
25+
// "AmazonQ" is shared across toolkits to provide a common access point, don't change it
26+
const name = 'AmazonQ'
27+
const manifest = await new ManifestResolver(manifestURL, name).resolve()
28+
const installationResult = await new LanguageServerResolver(
29+
manifest,
30+
name,
31+
new Range(supportedLspServerVersions)
32+
).resolve()
33+
34+
// TODO Cleanup old versions of language servers
35+
return installationResult
36+
}
37+
}

packages/amazonq/test/unit/amazonq/lsp/lspController.test.ts

Lines changed: 0 additions & 51 deletions
This file was deleted.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,11 @@ 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) {
175+
export async function activate(extensionContext: ExtensionContext, serverModule: string) {
176176
LspClient.instance
177177
const toDispose = extensionContext.subscriptions
178178

179179
let rangeFormatting: Disposable | undefined
180-
// The server is implemented in node
181-
const serverModule = path.join(extensionContext.extensionPath, 'resources/qserver/lspServer.js')
182180
// The debug options for the server
183181
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
184182
const debugOptions = { execArgv: ['--nolazy', '--preserve-symlinks', '--stdio'] }

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

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ import { telemetry } from '../../shared/telemetry'
1414
import { isCloud9 } from '../../shared/extensionUtilities'
1515
import globals, { isWeb } from '../../shared/extensionGlobals'
1616
import { isAmazonInternalOs } from '../../shared/vscode/env'
17-
import { LspDownloader, Manifest } from '../../shared/fetchLsp'
18-
import { fs } from '../../shared/fs/fs'
19-
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
17+
import { WorkspaceLSPResolver } from './workspaceInstaller'
2018

2119
export interface Chunk {
2220
readonly filePath: string
@@ -25,13 +23,6 @@ export interface Chunk {
2523
readonly relativePath?: string
2624
readonly programmingLanguage?: string
2725
}
28-
29-
const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json'
30-
// this LSP client in Q extension is only going to work with these LSP server versions
31-
const supportedLspServerVersions = ['0.1.32']
32-
33-
const nodeBinName = process.platform === 'win32' ? 'node.exe' : 'node'
34-
3526
export interface BuildIndexConfig {
3627
startUrl?: string
3728
maxIndexSize: number
@@ -49,22 +40,14 @@ export interface BuildIndexConfig {
4940
* Pre-process the input to Index Files API
5041
* Post-process the output from Query API
5142
*/
52-
export class LspController extends LspDownloader {
43+
export class LspController {
5344
static #instance: LspController
5445
private _isIndexingInProgress = false
55-
private serverPath: string
56-
private nodePath: string
5746

5847
public static get instance() {
5948
return (this.#instance ??= new this())
6049
}
6150

62-
constructor() {
63-
super(manifestUrl, supportedLspServerVersions)
64-
this.serverPath = globals.context.asAbsolutePath(path.join('resources', 'qserver'))
65-
this.nodePath = globals.context.asAbsolutePath(path.join('resources', nodeBinName))
66-
}
67-
6851
isIndexingInProgress() {
6952
return this._isIndexingInProgress
7053
}
@@ -170,67 +153,16 @@ export class LspController extends LspDownloader {
170153
}
171154
}
172155

173-
async isLspInstalled(): Promise<boolean> {
174-
return (await fs.exists(this.serverPath)) && (await fs.exists(this.nodePath))
175-
}
176-
177-
async cleanup(): Promise<boolean> {
178-
if (await fs.exists(this.serverPath)) {
179-
await tryRemoveFolder(this.serverPath)
180-
}
181-
182-
if (await fs.exists(this.nodePath)) {
183-
await fs.delete(this.nodePath)
184-
}
185-
186-
return true
187-
}
188-
189-
async install(manifest: Manifest) {
190-
const server = this.getDependency(manifest, 'qserver')
191-
const runtime = this.getDependency(manifest, 'node')
192-
if (!server || !runtime) {
193-
getLogger('lsp').info(`Did not find LSP URL for ${process.platform} ${process.arch}`)
194-
return false
195-
}
196-
197-
let tempFolder = undefined
198-
199-
try {
200-
tempFolder = await makeTemporaryToolkitFolder()
201-
await this.downloadAndExtractServer({
202-
content: server,
203-
installLocation: this.serverPath,
204-
name: 'qserver',
205-
tempFolder,
206-
extractToTempFolder: true,
207-
})
208-
209-
const runtimeTempPath = path.join(tempFolder, nodeBinName)
210-
await this.installRuntime(runtime, this.nodePath, runtimeTempPath)
211-
} finally {
212-
// clean up temp folder
213-
if (tempFolder) {
214-
await tryRemoveFolder(tempFolder)
215-
}
216-
}
217-
218-
return true
219-
}
220-
221156
async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) {
222157
if (isCloud9() || isWeb() || isAmazonInternalOs()) {
223158
getLogger().warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ')
224159
// do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+)
225160
return
226161
}
227162
setImmediate(async () => {
228-
const ok = await LspController.instance.tryInstallLsp()
229-
if (!ok) {
230-
return
231-
}
232163
try {
233-
await activateLsp(context)
164+
const installResult = await new WorkspaceLSPResolver().resolve()
165+
await activateLsp(context, path.join(installResult.assetDirectory, 'resources/qserver/lspServer.js'))
234166
getLogger().info('LspController: LSP activated')
235167
void LspController.instance.buildIndex(buildIndexConfig)
236168
// log the LSP server CPU and Memory usage per 30 minutes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { LspResolver, LspResult } from '../../shared/languageServer/types'
7+
import { ManifestResolver } from '../../shared/languageServer/manifestResolver'
8+
import { LanguageServerResolver } from '../../shared/languageServer/lspResolver'
9+
import { Range } from 'semver'
10+
11+
const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json'
12+
// this LSP client in Q extension is only going to work with these LSP server versions
13+
const supportedLspServerVersions = '0.1.32'
14+
15+
export class WorkspaceLSPResolver implements LspResolver {
16+
async resolve(): Promise<LspResult> {
17+
const name = 'AmazonQ-Workspace'
18+
const manifest = await new ManifestResolver(manifestUrl, name).resolve()
19+
const installationResult = await new LanguageServerResolver(
20+
manifest,
21+
name,
22+
new Range(supportedLspServerVersions)
23+
).resolve()
24+
25+
// TODO Cleanup old versions of language servers
26+
return installationResult
27+
}
28+
}

packages/core/src/shared/crypto.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@
2424
import { isWeb } from './extensionGlobals'
2525

2626
export function randomUUID(): `${string}-${string}-${string}-${string}-${string}` {
27+
return getCrypto().randomUUID()
28+
}
29+
30+
function getCrypto() {
2731
if (isWeb()) {
28-
return globalThis.crypto.randomUUID()
32+
return globalThis.crypto
2933
}
3034

31-
return require('crypto').randomUUID()
35+
return require('crypto')
3236
}
3337

3438
/**
@@ -54,3 +58,10 @@ export function truncateUuid(uuid: string) {
5458
const cleanedUUID = uuid.replace(/-/g, '')
5559
return `${cleanedUUID.substring(0, 4)}...${cleanedUUID.substring(cleanedUUID.length - 4)}`
5660
}
61+
62+
export function createHash(algorithm: string, contents: string | Buffer): string {
63+
const crypto = getCrypto()
64+
const hash = crypto.createHash(algorithm)
65+
hash.update(contents)
66+
return `${algorithm}:${hash.digest('hex')}`
67+
}

0 commit comments

Comments
 (0)