From fffc4557099ab55c0dc344d72046344575d54c37 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 4 Feb 2026 15:57:06 +0100 Subject: [PATCH] Debugging instructions simplified --- BUILD.MD | 56 ++++++++---------- vscode/package.json | 2 +- vscode/src/extension.ts | 79 ++++++++++++++----------- vscode/src/nbcode.ts | 75 ++++++++++++++++------- vscode/src/test/suite/extension.test.ts | 3 +- vscode/tsconfig.json | 1 + 6 files changed, 126 insertions(+), 90 deletions(-) diff --git a/BUILD.MD b/BUILD.MD index bb8c561..dcc905c 100644 --- a/BUILD.MD +++ b/BUILD.MD @@ -23,7 +23,7 @@ ## Prerequisities -- JDK, version 17 or later +- JDK, version 21 or later - Ant, latest version - Maven, latest version - node.js, latest LTS (to build VSIX) @@ -33,7 +33,8 @@ ```bash $ git clone --recurse-submodules https://github.com/apache/netbeans-vscode.git -$ cd netbeans-vscode/ +$ cd netbeans-vscode +netbeans-vscode$ git submodules update --init ``` ## Building @@ -44,7 +45,8 @@ To build the VS Code extension invoke: netbeans-vscode$ ant build-netbeans netbeans-vscode$ ant build-vscode-ext ``` -The resulting extension is then in the `build` directory, with the `.vsix` extension. +The resulting VSCode extension is then generated in the `build` directory - +a file with the `.vsix` extension. The typical file name is `apache-netbeans-java-0.1.0.vsix` - the version can be changed by using the `-Dvsix.version=x.y.z` property - that's what [continuous integration server](https://ci-builds.apache.org/job/Netbeans/job/netbeans-vscode/) @@ -122,51 +124,40 @@ vscode$ code --extensionDevelopmentPath=`pwd` path_to_project Or you can open the `vscode` folder in `code` directly and use **F5** to debug the extension's *typescript code*. -To debug the *Java code*, launch the NetBeans part of the VS Code system first -and specify suitable debug arguments to start _standalone NBLS_ instance: +To debug the *Java code*, use following prepared script to launch `code` with +necessary arguments: ```bash -vscode$ npm run nbcode -- --jdkhome /jdk -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 +vscode$ npm run nbcode ``` - -To add extra modules while debugging the NetBeans part -```bash -vscode$ npm run nbcode -- -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000 -J-Dnetbeans.extra.dirs=/path/to/extension -``` - -Connect to the process with Java debugger, setup all breakpoints. Then launch -the VS Code extension (which connects to the already running _standalone NBLS_ Java process): - +The script needs to have `code` and JDK available on classpath. It prints +out the command it attempts to invoke. One can either copy the command and +alter it, when necessary, but one can also provide extra arguments after `--`. +For example to add extra modules while debugging the NetBeans part ```bash -vscode$ code --extensionDevelopmentPath=`pwd` path_to_the_maven_project +vscode$ npm run nbcode -- -J-Dnetbeans.extra.dirs=/path/to/extension ``` -To start from a clean state, following +The JPDA protocol listens on port 8000. Connect to the process with Java debugger, +setup all breakpoints. To start from a clean state the following [CLI options](https://code.visualstudio.com/docs/editor/command-line) -maybe of an interest: +are being passed to the `code` by default: - `--user-data-dir` - clean any user settings with this option - `--extensions-dir` - avoid 3rd party extensions using this option -**Important note**: when `--user-data-dir` is used, the _standalone NBLS_ must be start as -```bash -vscode$ nbcode_userdir=/the-code-user-data-dir npm run nbcode -- --jdkhome /jdk -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 -``` -So that the vscode can connect to the _standalone NBLS_ instance. +The script also instructs the system to print messages (stderr, out) to the console +by adding `-J-Dnetbeans.logger.console=true` to the npm commandline. +This has the same effect as `netbeans.verbose = true` settings in the vscode. + + +### LSP protocol tracing +Messages from the LSP protocol can be displayed in vscode by setting `java.trace.server = verbose` setting in vscode JSON settings. Sometimes it may be needed to record LSP requests and responses in the debug output log stream from the Apache NetBeans language server so that requests are properly ordered with logs from executed actions. This can be enabled on language server startup by adding `-J-Dorg.netbeans.modules.java.lsp.server.lsptrace.level=FINEST` +to the NBLS commandline. diff --git a/vscode/package.json b/vscode/package.json index 3ee87d2..296d367 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -1516,7 +1516,7 @@ "compile": "tsc -p ./ && node ./esbuild.js", "watch": "tsc -watch -p ./ | node ./esbuild.js --watch", "test": "node ./out/test/runTest.js", - "nbcode": "node ./out/nbcode.js", + "nbcode": "npm run compile && node ./out/nbcode.js", "nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install .*nbjavac.*", "apisupport": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install '(org.netbeans.libs.xerces|org.netbeans.modules.editor.structure|org.netbeans.modules.xml|org.netbeans.modules.xml.axi|org.netbeans.modules.xml.retriever|org.netbeans.modules.xml.schema.model|org.netbeans.modules.xml.tax|org.netbeans.modules.xml.text|org.netbeans.modules.ant.browsetask|.*apisupport.*|org.netbeans.modules.debugger.jpda.ant)' && node ./out/nbcode.js -J-Dnetbeans.close=true --modules --enable .*apisupport.ant" }, diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 14294c2..37a15c7 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -52,7 +52,7 @@ import * as launcher from './nbcode'; import {NbTestAdapter} from './testAdapter'; import { asRanges, StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest, MutliStepInputRequest, TestProgressNotification, DebugConnector, TextEditorDecorationCreateRequest, TextEditorDecorationSetNotification, TextEditorDecorationDisposeNotification, HtmlPageRequest, HtmlPageParams, - ExecInHtmlPageRequest, SetTextEditorDecorationParams, ProjectActionParams, UpdateConfigurationRequest, QuickPickStep, InputBoxStep, SaveDocumentsRequest, SaveDocumentRequestParams, OutputMessage, WriteOutputRequest, ShowOutputRequest, CloseOutputRequest, ResetOutputRequest + ExecInHtmlPageRequest, SetTextEditorDecorationParams, ProjectActionParams, UpdateConfigurationRequest, QuickPickStep, InputBoxStep, SaveDocumentsRequest, SaveDocumentRequestParams, OutputMessage, WriteOutputRequest, ShowOutputRequest, CloseOutputRequest, ResetOutputRequest } from './protocol'; import * as launchConfigurations from './launchConfigurations'; import { createTreeViewService, TreeViewService, TreeItemDecorator, Visualizer, CustomizableTreeDataProvider } from './explorer'; @@ -67,6 +67,7 @@ import * as sshGuide from './panels/SshGuidePanel'; import * as runImageGuide from './panels/RunImageGuidePanel'; import { shouldHideGuideFor } from './panels/guidesUtil'; import { SSHSession } from './ssh/ssh'; +import { env } from 'process'; const API_VERSION : string = "1.0"; export const COMMAND_PREFIX : string = "nbls"; @@ -132,30 +133,37 @@ export function enableConsoleLog() { console.log("enableConsoleLog"); } -export function findClusters(myPath : string): string[] { - let clusters = []; +export function findClusters(myPath : string, log: vscode.OutputChannel): string[] { + let clusters: string[] = []; for (let e of vscode.extensions.all) { if (e.extensionPath === myPath) { continue; } const dir = path.join(e.extensionPath, 'nbcode'); - if (!fs.existsSync(dir)) { - continue; - } - const exists = fs.readdirSync(dir); - for (let clusterName of exists) { - let clusterPath = path.join(dir, clusterName); - let clusterModules = path.join(clusterPath, 'config', 'Modules'); - if (!fs.existsSync(clusterModules)) { - continue; + searchAddCluster(dir); + } + return clusters; + + function searchAddCluster(rootWithClusters: string) { + if (fs.existsSync(rootWithClusters)) { + const exists = fs.readdirSync(rootWithClusters); + for (let clusterName of exists) { + let clusterPath = path.join(rootWithClusters, clusterName); + addCluster(clusterPath); } + } + } + function addCluster(clusterPath: string): boolean { + let clusterModules = path.join(clusterPath, 'config', 'Modules'); + if (fs.existsSync(clusterModules)) { let perm = fs.statSync(clusterModules); if (perm.isDirectory()) { clusters.push(clusterPath); + return true; } } + return false; } - return clusters; } // for tests only ! @@ -397,7 +405,7 @@ class LineBufferingPseudoterminal implements vscode.Pseudoterminal { private closeEmitter = new vscode.EventEmitter(); onDidClose?: vscode.Event = this.closeEmitter.event; - private buffer: string = ''; + private buffer: string = ''; private isOpen = false; private readonly name: string; private terminal: vscode.Terminal | undefined; @@ -476,7 +484,7 @@ class LineBufferingPseudoterminal implements vscode.Pseudoterminal { } }); } - // Prevent 'stealing' of the focus when running tests in parallel + // Prevent 'stealing' of the focus when running tests in parallel if (!testAdapter?.testInParallelProfileExist()) { this.terminal.show(true); } @@ -494,12 +502,12 @@ class LineBufferingPseudoterminal implements vscode.Pseudoterminal { this.instances.set(name, instance); } const instance = this.instances.get(name)!; - instance.show(); + instance.show(); return instance; } } -export function activate(context: ExtensionContext): VSNetBeansAPI { +export function activate(context: ExtensionContext): VSNetBeansAPI { const provider = new StringContentProvider(); const scheme = 'in-memory'; const providerRegistration = vscode.workspace.registerTextDocumentContentProvider(scheme, provider); @@ -511,7 +519,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { if (selected == POLICIES_UPLOAD) { await vscode.commands.executeCommand('nbls.cloud.assets.policy.upload'); return; - } + } const content = await vscode.commands.executeCommand('nbls.cloud.assets.policy.create.local') as string; const document = vscode.Uri.parse(`${scheme}:policies.txt?${encodeURIComponent(content)}`); @@ -530,7 +538,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { return; } else if (selected == CONFIG_TO_OKE_CM) { await commands.executeCommand('nbls.cloud.assets.configmap.upload'); - return; + return; } const content = await vscode.commands.executeCommand('nbls.cloud.assets.config.create.local') as string; const document = vscode.Uri.parse(`${scheme}:application.properties?${encodeURIComponent(content)}`); @@ -621,9 +629,9 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { }); } } - let warnedJDKs : string[] = specifiedJDKWarned; + let warnedJDKs : string[] = specifiedJDKWarned; if (!jdkOK && !warnedJDKs.includes(specifiedJDK || '')) { - const msg = specifiedJDK ? + const msg = specifiedJDK ? `The current path to JDK "${specifiedJDK}" may be invalid. A valid JDK ${MINIMAL_JDK_VERSION}+ is required by Apache NetBeans Language Server to run. You should configure a proper JDK for Apache NetBeans and/or other technologies. Do you want to run JDK configuration now?` : `A valid JDK ${MINIMAL_JDK_VERSION}+ is required by Apache NetBeans Language Server to run, but none was found. You should configure a proper JDK for Apache NetBeans and/or other technologies. ` + @@ -637,7 +645,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { warnedJDKs.push(specifiedJDK || ''); } } - let currentClusters = findClusters(context.extensionPath).sort(); + let currentClusters = findClusters(context.extensionPath, log).sort(); const dsSorter = (a: TextDocumentFilter, b: TextDocumentFilter) => { return (a.language || '').localeCompare(b.language || '') || (a.pattern || '').localeCompare(b.pattern || '') @@ -646,7 +654,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { let currentDocumentSelectors = collectDocumentSelectors().sort(dsSorter); context.subscriptions.push(vscode.extensions.onDidChange(() => { checkConflict(); - const newClusters = findClusters(context.extensionPath).sort(); + const newClusters = findClusters(context.extensionPath, log).sort(); const newDocumentSelectors = collectDocumentSelectors().sort(dsSorter); if (newClusters.length !== currentClusters.length || newDocumentSelectors.length !== currentDocumentSelectors.length || newClusters.find((value, index) => value !== currentClusters[index]) || newDocumentSelectors.find((value, index) => value !== currentDocumentSelectors[index])) { @@ -900,7 +908,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { } class P implements vscode.DebugConfigurationProvider { config : vscode.DebugConfiguration | undefined; - + resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult { this.config = debugConfiguration; return undefined; @@ -953,7 +961,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { } else { debugConfig['mainClass'] = docUri.toString(); } - + if (testInParallel) { debugConfig['testInParallel'] = testInParallel; } @@ -961,7 +969,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { debugConfig['projects'] = projects; } - const ret = await vscode.debug.startDebugging(workspaceFolder, debugConfig); + const ret = await vscode.debug.startDebugging(workspaceFolder, debugConfig); return ret ? new Promise((resolve) => { const listener = vscode.debug.onDidTerminateDebugSession(() => { listener.dispose(); @@ -971,11 +979,11 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { } }; - context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.run.test.parallel', async (projects?) => { + context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.run.test.parallel', async (projects?) => { testAdapter?.runTestsWithParallelProfile(projects); })); - context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.run.test.parallel.createProfile', async (projects?) => { + context.subscriptions.push(commands.registerCommand(COMMAND_PREFIX + '.run.test.parallel.createProfile', async (projects?) => { testAdapter?.registerRunInParallelProfile(projects); })); @@ -1151,7 +1159,7 @@ export function activate(context: ExtensionContext): VSNetBeansAPI { */ let maintenance : Promise | null; -function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean, +function activateWithJDK(specifiedJDK: string | null, context: ExtensionContext, log : vscode.OutputChannel, notifyKill: boolean, clientResolve? : (x : NbLanguageClient) => void, clientReject? : (x : any) => void): void { let oldClient = client; let setClient : [(c : NbLanguageClient) => void, (err : any) => void]; @@ -1293,12 +1301,13 @@ function doActivateWithJDK(promise: Promise, specifiedJDK: str } let info = { - clusters : findClusters(context.extensionPath), + clusters : findClusters(context.extensionPath, log), + debug: env['netbeans_debug'] ? '-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000' : null, extensionPath: context.extensionPath, storagePath : userdir, jdkHome : specifiedJDK, verbose: beVerbose - }; + }; let launchMsg = `Launching Apache NetBeans Language Server with ${specifiedJDK ? specifiedJDK : 'default system JDK'} and userdir ${userdir}`; handleLog(log, launchMsg); vscode.window.setStatusBarMessage(launchMsg, 2000); @@ -1443,9 +1452,9 @@ function doActivateWithJDK(promise: Promise, specifiedJDK: str errorHandler: { error : function(error: Error, _message: Message, count: number): ErrorHandlerResult { startupError = error.message; - return { - action: started ? ErrorAction.Continue : ErrorAction.Shutdown, - message: error.message + return { + action: started ? ErrorAction.Continue : ErrorAction.Shutdown, + message: error.message }; }, closed : function(): CloseHandlerResult { @@ -1514,7 +1523,7 @@ function doActivateWithJDK(promise: Promise, specifiedJDK: str if (uriList.includes(uri)) { ed.save(); continue; - } + } if (uri.startsWith("file:///")) { // make file:/// just file:/ uri = "file:/" + uri.substring(8); diff --git a/vscode/src/nbcode.ts b/vscode/src/nbcode.ts index 0450aa5..3cf94df 100644 --- a/vscode/src/nbcode.ts +++ b/vscode/src/nbcode.ts @@ -26,6 +26,7 @@ import { Readable } from 'stream'; import { env } from 'process'; export interface LaunchInfo { + debug: string | unknown; clusters: string[]; extensionPath: string; storagePath: string; @@ -34,8 +35,8 @@ export interface LaunchInfo { } function find(info: LaunchInfo): string { - let nbcode = os.platform() === 'win32' ? - os.arch() === 'x64' ? 'nbcode64.exe' : 'nbcode.exe' + let nbcode = os.platform() === 'win32' ? + os.arch() === 'x64' ? 'nbcode64.exe' : 'nbcode.exe' : 'nbcode'; let nbcodePath = path.join(info.extensionPath, "nbcode", "bin", nbcode); @@ -74,10 +75,10 @@ export function launch( ideArgs.push(...env['netbeans_extra_options'].split(' ')); } ideArgs.push(...extraArgs); - if (env['netbeans_debug'] && extraArgs && extraArgs.find(s => s.includes("--list"))) { - ideArgs.push(...['-J-Dnetbeans.logger.console=true', '-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000']); + let debugArgs = info.debug + if (typeof debugArgs === 'string') { + ideArgs.push(...['-J-Dnetbeans.logger.console=true', debugArgs]); } - console.log(`Launching NBLS with arguments: ` + ideArgs); let process: ChildProcessByStdio = spawn(nbcodePath, ideArgs, { @@ -97,30 +98,58 @@ if (typeof process === 'object' && typeof process.argv0 ==='string' && process.a let args = process.argv.slice(2); let json = JSON.parse("" + fs.readFileSync(path.join(extension, 'package.json'))); let storage; - - if (!env.nbcode_userdir || env.nbcode_userdir == 'global') { - storage = path.join(os.platform() === 'darwin' ? - path.join(os.homedir(), 'Library', 'Application Support') : - path.join(os.homedir(), '.config'), - 'Code', 'User', 'globalStorage', json.publisher + '.' + json.name); + let datadir; + if (!env.nbcode_userdir) { + datadir= path.join(process.cwd(), "out", "userdir"); + storage = path.join(datadir, json.publisher + '.' + json.name); + } else if (env.nbcode_userdir == 'global') { + if (os.platform() === 'darwin') { + datadir = path.join(os.homedir(), 'Library', 'Application Support'); + } else { + datadir = path.join(os.homedir(), '.config', 'Code', 'User', 'globalStorage'); + } + storage = path.join(datadir, json.publisher + '.' + json.name); } else { + datadir= path.join(process.cwd(), "out", "userdir"); storage = env.nbcode_userdir; } - console.log('Launching NBLS with user directory: ' + storage) - let info = { - clusters : clusters, - extensionPath: extension, - storagePath : storage, - jdkHome : null - }; - let p = launch(info, ...args); - p.stdout.on('data', function(data) { + let extdir = path.join(process.cwd(), "out", "extdir") + let codeArgs = [ + "--extensionDevelopmentPath=" + process.cwd(), + "--user-data-dir=" + datadir, + "--extensions-dir=" + extdir + ].concat(args); + let extraEnv: NodeJS.ProcessEnv = { + netbeans_debug : '-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000' + } + console.log('Launching `code` with following arguments:'); + for (var arg of codeArgs) { + console.log(` ${arg}`); + } + console.log('and following additional environment variables:'); + for (var name in extraEnv) { + console.log(` ${name}=${extraEnv[name]}`); + } + + let codeProc: ChildProcessByStdio = spawn("code", codeArgs, { + stdio : ["ignore", "pipe", "pipe"], + cwd : process.cwd(), + env : Object.assign(process.env, extraEnv) + }); + codeProc.stdout.on('data', function(data) { console.log(data.toString()); }); - p.stderr.on('data', function(data) { + codeProc.stderr.on('data', function(data) { console.log(data.toString()); }); - p.on('close', (code) => { - console.log(`nbcode finished with status ${code}`); + codeProc.on('error', (data) => { + console.error(`code yielded an error: ${data} and ${data.stack}`); + }); + codeProc.on('message', (data) => { + console.error(`code message: ${data}`); + }); + codeProc.on('close', (code) => { + console.log(`code finished with status ${code}, exiting the launcher script`); + process.exit(code); }); } diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts index 2ff476d..cf92f24 100644 --- a/vscode/src/test/suite/extension.test.ts +++ b/vscode/src/test/suite/extension.test.ts @@ -52,10 +52,11 @@ suite('Extension Test Suite', () => { test('Find clusters', async () => { const nbcode = vscode.extensions.getExtension('asf.apache-netbeans-java'); + let log = vscode.window.createOutputChannel("Apache NetBeans Language Server"); assert.ok(nbcode); const extraCluster = path.join(nbcode.extensionPath, "nbcode", "extra"); - let clusters = myExtension.findClusters('non-existent'). + let clusters = myExtension.findClusters('non-existent', log). // ignore 'extra' cluster in the extension path, since nbjavac is there during development: filter(s => !s.startsWith(extraCluster)); diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json index 64351bb..a354b97 100644 --- a/vscode/tsconfig.json +++ b/vscode/tsconfig.json @@ -16,6 +16,7 @@ // "noUnusedParameters": true, /* Report errors on unused parameters. */ }, "exclude": [ + "out", "node_modules", ".vscode-test" ]