Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Bug Fix",
"description": "Enable Amazon Q LSP in AL2 instances"
}
6 changes: 4 additions & 2 deletions packages/amazonq/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'

Expand Down Expand Up @@ -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)) {
Expand Down
34 changes: 27 additions & 7 deletions packages/amazonq/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
getLogger,
undefinedIfEmpty,
getOptOutPreference,
isAmazonInternalOs,
fs,
} from 'aws-core-vscode/shared'
import { activate } from './chat/activation'
import { AmazonQResourcePaths } from './lspInstaller'
Expand All @@ -40,6 +42,10 @@ import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './con
const localize = nls.loadMessageBundle()
const logger = getLogger('amazonqLsp.lspClient')

export async function hasGlibcPatch(): Promise<boolean> {
return await fs.exists('/opt/vsc-sysroot/lib64/ld-linux-x86-64.so.2')
Copy link
Contributor Author

@leigaol leigaol May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow up: Check if aarch64 has this issue or not.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you've only configured this for x86_64. If you want this to work on aarch64, then you'll need to point to ld-linux-aarch64.so.1.

Please check out my overall comment.

}

export async function startLanguageServer(
extensionContext: vscode.ExtensionContext,
resourcePaths: AmazonQResourcePaths
Expand All @@ -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 = {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/amazonq/lsp/lspClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,15 @@ 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,
})

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 = {
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/shared/lsp/utils/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)))
Expand Down Expand Up @@ -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()
Expand Down
Loading