diff --git a/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json b/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json new file mode 100644 index 00000000000..59ee9d3498e --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Enable Amazon Q LSP in AL2 instances" +} diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index ffcbf333762..5ae9e397119 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -33,6 +33,7 @@ import { maybeShowMinVscodeWarning, Experiments, isSageMaker, + isAmazonInternalOs, } from 'aws-core-vscode/shared' import { ExtStartUpSources } from 'aws-core-vscode/telemetry' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' @@ -43,7 +44,7 @@ import { registerCommands } from './commands' import { focusAmazonQPanel } from 'aws-core-vscode/codewhispererChat' import { activate as activateAmazonqLsp } from './lsp/activation' import { activate as activateInlineCompletion } from './app/inline/activation' -import { isAmazonInternalOs } from 'aws-core-vscode/shared' +import { hasGlibcPatch } from './lsp/client' export const amazonQContextPrefix = 'amazonq' @@ -122,9 +123,10 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is await activateCodeWhisperer(extContext as ExtContext) if ( (Experiments.instance.get('amazonqLSP', true) || Auth.instance.isInternalAmazonUser()) && - !isAmazonInternalOs() + (!isAmazonInternalOs() || (await hasGlibcPatch())) ) { // start the Amazon Q LSP for internal users first + // for AL2, start LSP if glibc patch is found await activateAmazonqLsp(context) } if (!Experiments.instance.get('amazonqLSPInline', false)) { diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index d813370cb0b..40f1cabb060 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -32,6 +32,8 @@ import { getLogger, undefinedIfEmpty, getOptOutPreference, + isAmazonInternalOs, + fs, } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' @@ -40,6 +42,10 @@ import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './con const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') +export async function hasGlibcPatch(): Promise { + return await fs.exists('/opt/vsc-sysroot/lib64/ld-linux-x86-64.so.2') +} + export async function startLanguageServer( extensionContext: vscode.ExtensionContext, resourcePaths: AmazonQResourcePaths @@ -55,19 +61,33 @@ export async function startLanguageServer( '--pre-init-encryption', '--set-credentials-encryption-key', ] - const serverOptions = createServerOptions({ - encryptionKey, - executable: resourcePaths.node, - serverModule, - execArgv: argv, - }) const documentSelector = [{ scheme: 'file', language: '*' }] const clientId = 'amazonq' const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`) + let executable: string[] = [] + // apply the GLIBC 2.28 path to node js runtime binary + if (isAmazonInternalOs() && (await hasGlibcPatch())) { + executable = [ + '/opt/vsc-sysroot/lib64/ld-linux-x86-64.so.2', + '--library-path', + '/opt/vsc-sysroot/lib64', + resourcePaths.node, + ] + getLogger('amazonqLsp').info(`Patched node runtime with GLIBC to ${executable}`) + } else { + executable = [resourcePaths.node] + } + + const serverOptions = createServerOptions({ + encryptionKey, + executable: executable, + serverModule, + execArgv: argv, + }) - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger) + await validateNodeExe(executable, resourcePaths.lsp, argv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index b992fef2fe3..bd671af0a39 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -255,7 +255,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths const serverOptions = createServerOptions({ encryptionKey: key, - executable: resourcePaths.node, + executable: [resourcePaths.node], serverModule, // TODO(jmkeyes): we always use the debug options...? execArgv: debugOptions.execArgv, @@ -263,7 +263,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths const documentSelector = [{ scheme: 'file', language: '*' }] - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger) + await validateNodeExe([resourcePaths.node], resourcePaths.lsp, debugOptions.execArgv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 5d96ef496f4..7db40ffdf30 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -29,9 +29,12 @@ function getEncryptionInit(key: Buffer): string { /** * Checks that we can actually run the `node` executable and execute code with it. */ -export async function validateNodeExe(nodePath: string, lsp: string, args: string[], logger: Logger) { +export async function validateNodeExe(nodePath: string[], lsp: string, args: string[], logger: Logger) { + const bin = nodePath[0] // Check that we can start `node` by itself. - const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' }) + const proc = new ChildProcess(bin, [...nodePath.slice(1), '-e', 'console.log("ok " + process.version)'], { + logging: 'no', + }) const r = await proc.run() const ok = r.exitCode === 0 && r.stdout.includes('ok') if (!ok) { @@ -41,7 +44,7 @@ export async function validateNodeExe(nodePath: string, lsp: string, args: strin } // Check that we can start `node …/lsp.js --stdio …`. - const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' }) + const lspProc = new ChildProcess(bin, [...nodePath.slice(1), lsp, ...args], { logging: 'no' }) try { // Start asynchronously (it never stops; we need to stop it below). lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true))) @@ -80,16 +83,17 @@ export function createServerOptions({ execArgv, }: { encryptionKey: Buffer - executable: string + executable: string[] serverModule: string execArgv: string[] }) { return async () => { - const args = [serverModule, ...execArgv] + const bin = executable[0] + const args = [...executable.slice(1), serverModule, ...execArgv] if (isDebugInstance()) { args.unshift('--inspect=6080') } - const lspProcess = new ChildProcess(executable, args) + const lspProcess = new ChildProcess(bin, args) // this is a long running process, awaiting it will never resolve void lspProcess.run()