From 65dfa7c3cdb6dcca79b422495514ebb505859bd7 Mon Sep 17 00:00:00 2001 From: Josh Arnold Date: Sun, 14 Dec 2025 19:25:32 -0600 Subject: [PATCH 1/2] Add VSCode extension for SourceKit Bazel BSP Introduces a TypeScript-based VSCode extension that integrates with the SourceKit Bazel BSP server. Includes basic extension scaffolding, logger utility, and build configuration. --- .github/workflows/ci.yaml | 5 + .../BuildTargets/BazelTargetStore.swift | 3 + .../SKOptions/AqueryResult.swift | 3 + .../WatchedFileChangeHandler.swift | 2 + .../MessageHandler/BSPMessageHandler.swift | 5 + .../Server/SourceKitBazelBSPServer.swift | 2 + .../SharedUtils/Logger.swift | 11 +- vscode-extension/.gitignore | 4 + vscode-extension/.vscode/launch.json | 33 +++++ vscode-extension/.vscode/tasks.json | 22 ++++ vscode-extension/.vscodeignore | 7 ++ vscode-extension/README.md | 11 ++ vscode-extension/package-lock.json | 58 +++++++++ vscode-extension/package.json | 32 +++++ vscode-extension/src/extension.ts | 44 +++++++ vscode-extension/src/logStream.ts | 114 ++++++++++++++++++ vscode-extension/src/logger.ts | 35 ++++++ vscode-extension/tsconfig.json | 19 +++ 18 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 vscode-extension/.gitignore create mode 100644 vscode-extension/.vscode/launch.json create mode 100644 vscode-extension/.vscode/tasks.json create mode 100644 vscode-extension/.vscodeignore create mode 100644 vscode-extension/README.md create mode 100644 vscode-extension/package-lock.json create mode 100644 vscode-extension/package.json create mode 100644 vscode-extension/src/extension.ts create mode 100644 vscode-extension/src/logStream.ts create mode 100644 vscode-extension/src/logger.ts create mode 100644 vscode-extension/tsconfig.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c6831e45..39518dd4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,6 +39,11 @@ jobs: run: | cd Example bazelisk test //HelloWorld:HelloWorldTests + - name: Build VSCode extension + run: | + cd vscode-extension + npm install + npm run compile - name: Build release source archive run: make release_source_archive - uses: actions/upload-artifact@v4 diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift b/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift index 055ce1b1..a2b42304 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift @@ -181,6 +181,9 @@ final class BazelTargetStoreImpl: BazelTargetStore { logger.warning( "Target \(uri.description, privacy: .public) has multiple top-level parents; will pick the first one: \(parentToUse, privacy: .public)" ) + extensionLogger.warning( + "Target \(uri.description, privacy: .public) has multiple top-level parents; will pick the first one: \(parentToUse, privacy: .public)" + ) } let rule = try topLevelRuleType(forBazelLabel: parentToUse) let config = try topLevelConfigInfo(forBazelLabel: parentToUse) diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift b/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift index 146f96ab..d97e4db2 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift @@ -39,6 +39,9 @@ struct AqueryResult: Hashable { logger.error( "Duplicate target found when aquerying (\(target.label))! This is unexpected. Will ignore the duplicate." ) + extensionLogger.error( + "Duplicate target found when aquerying (\(target.label))! This is unexpected. Will ignore the duplicate." + ) } result[target.label] = target } diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift b/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift index b5cd447d..c17d371b 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift @@ -95,6 +95,7 @@ final class WatchedFileChangeHandler { } } catch { logger.error("Error calculating deleted targets: \(error, privacy: .public)") + extensionLogger.error("Error calculating deleted targets: \(error, privacy: .public)") return [] } }() @@ -135,6 +136,7 @@ final class WatchedFileChangeHandler { } } catch { logger.error("Error calculating created targets: \(error, privacy: .public)") + extensionLogger.error("Error calculating created targets: \(error, privacy: .public)") return [] } }() diff --git a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift index 3c764e4c..5e726154 100644 --- a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift +++ b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift @@ -66,6 +66,7 @@ final class BSPMessageHandler: MessageHandler { try handler(notification) } catch { logger.error("Error while handling BSP notification: \(error.localizedDescription, privacy: .public)") + extensionLogger.error("Error while handling BSP notification: \(error.localizedDescription, privacy: .public)") } } @@ -86,11 +87,15 @@ final class BSPMessageHandler: MessageHandler { logger.error( "Error while replying to \(Request.method, privacy: .public): \(error.localizedDescription, privacy: .public)" ) + extensionLogger.error( + "Error while replying to \(Request.method, privacy: .public): \(error.localizedDescription, privacy: .public)" + ) reply(.failure(buildLSPError(error))) } } } catch { logger.error("Error while handling BSP request: \(error.localizedDescription, privacy: .public)") + extensionLogger.error("Error while handling BSP request: \(error.localizedDescription, privacy: .public)") reply(.failure(buildLSPError(from: error))) } } diff --git a/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift b/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift index 7984e4b4..0664d67c 100644 --- a/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift +++ b/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift @@ -129,6 +129,7 @@ package final class SourceKitBazelBSPServer { /// we get a shutdown request from sourcekit-lsp. package func run(parkThread: Bool = true) { logger.info("Connecting to sourcekit-lsp...") + extensionLogger.notice("BSP server starting...") connection.start( receiveHandler: handler, @@ -144,6 +145,7 @@ package final class SourceKitBazelBSPServer { } logger.info("Connection established, parking thread.") + extensionLogger.notice("BSP server connected to sourcekit-lsp ✓") // Park the thread by sleeping for 10 years. // All request handling is done on other threads and sourcekit-bazel-bsp exits by calling `_Exit` when it receives a diff --git a/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift b/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift index 54930a1e..84a36ee0 100644 --- a/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift +++ b/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift @@ -20,7 +20,16 @@ import Foundation import OSLog +private let bspSubsystem = "com.spotify.sourcekit-bazel-bsp" + /// Simple helper to create loggers under the `com.spotify.sourcekit-bazel-bsp` subsystem. package func makeFileLevelBSPLogger(withCategory category: String = #fileID) -> Logger { - Logger(subsystem: "com.spotify.sourcekit-bazel-bsp", category: category) + Logger(subsystem: bspSubsystem, category: category) } + +/// Logger specifically for messages intended to be shown in the VSCode extension. +/// Use this for user-facing status updates, not internal debugging. +/// +/// The extension filters for this category using: +/// `log stream --process sourcekit-bazel-bsp --predicate 'category == "extension"'` +package let extensionLogger = Logger(subsystem: bspSubsystem, category: "extension") diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 00000000..db75a72c --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +out/ +*.vsix + diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json new file mode 100644 index 00000000..d58eca55 --- /dev/null +++ b/vscode-extension/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: compile" + }, + { + "type": "swift", + "request": "launch", + "args": [], + "cwd": "${workspaceFolder:vscode-extension}", + "name": "Debug sourcekit-bazel-bsp", + "target": "sourcekit-bazel-bsp", + "configuration": "debug", + "preLaunchTask": "swift: Build Debug sourcekit-bazel-bsp" + }, + { + "type": "swift", + "request": "launch", + "args": [], + "cwd": "${workspaceFolder:vscode-extension}", + "name": "Release sourcekit-bazel-bsp", + "target": "sourcekit-bazel-bsp", + "configuration": "release", + "preLaunchTask": "swift: Build Release sourcekit-bazel-bsp" + } + ] +} diff --git a/vscode-extension/.vscode/tasks.json b/vscode-extension/.vscode/tasks.json new file mode 100644 index 00000000..e249bf79 --- /dev/null +++ b/vscode-extension/.vscode/tasks.json @@ -0,0 +1,22 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "compile", + "problemMatcher": "$tsc", + "label": "npm: compile", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "label": "npm: watch", + "isBackground": true + } + ] +} diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 00000000..812cacb1 --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,7 @@ +.vscode/** +src/** +tsconfig.json +**/*.ts +**/*.map +node_modules/** + diff --git a/vscode-extension/README.md b/vscode-extension/README.md new file mode 100644 index 00000000..d183b9c1 --- /dev/null +++ b/vscode-extension/README.md @@ -0,0 +1,11 @@ +# VSCode Extension + +This is an early WIP VSCode extension. It has the goal of enabling support for functionality such as: + +- [ ] (Cursor / VSCode): Automatic generation of build, launch, and debug tasks + +- [ ] (Cursor / VSCode): Test explorer & ability to run tests from within the IDE by clicking the tests individually, similarly to Xcode + +- [ ] Automatic index and build graph updates when adding / deleting files and targets (in other words, allowing the user to make heavy changes to the project without needing to restart the IDE) + +The extension is in active development and is not yet ready for use. diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json new file mode 100644 index 00000000..14a2e9eb --- /dev/null +++ b/vscode-extension/package-lock.json @@ -0,0 +1,58 @@ +{ + "name": "sourcekit-bazel-bsp", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sourcekit-bazel-bsp", + "version": "0.0.1", + "devDependencies": { + "@types/node": "^20.0.0", + "@types/vscode": "^1.85.0", + "typescript": "^5.3.0" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.107.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.107.0.tgz", + "integrity": "sha512-XS8YE1jlyTIowP64+HoN30OlC1H9xqSlq1eoLZUgFEC8oUTO6euYZxti1xRiLSfZocs4qytTzR6xCBYtioQTCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 00000000..314791f8 --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,32 @@ +{ + "name": "sourcekit-bazel-bsp", + "displayName": "SourceKit Bazel BSP", + "description": "Companion extension for sourcekit-bazel-bsp", + "version": "0.0.1", + "publisher": "spotify", + "repository": { + "type": "git", + "url": "https://github.com/spotify/sourcekit-bazel-bsp" + }, + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Programming Languages" + ], + "activationEvents": [ + "onStartupFinished" + ], + "main": "./out/extension.js", + "contributes": {}, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./" + }, + "devDependencies": { + "@types/vscode": "^1.85.0", + "@types/node": "^20.0.0", + "typescript": "^5.3.0" + } +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts new file mode 100644 index 00000000..9931bc11 --- /dev/null +++ b/vscode-extension/src/extension.ts @@ -0,0 +1,44 @@ +// Copyright (c) 2025 Spotify AB. +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import * as vscode from "vscode"; +import { initLogger, log } from "./logger"; +import { LogStreamManager } from "./logStream"; + +let logStreamManager: LogStreamManager | undefined; + +export function activate(context: vscode.ExtensionContext) { + const outputChannel = initLogger(); + log("Extension activated ✅"); + + // Start capturing BSP server logs + logStreamManager = new LogStreamManager(outputChannel); + logStreamManager.start(); + + context.subscriptions.push({ + dispose: () => { + logStreamManager?.stop(); + }, + }); +} + +export function deactivate() { + logStreamManager?.stop(); + logStreamManager = undefined; +} diff --git a/vscode-extension/src/logStream.ts b/vscode-extension/src/logStream.ts new file mode 100644 index 00000000..5b44bad4 --- /dev/null +++ b/vscode-extension/src/logStream.ts @@ -0,0 +1,114 @@ +// Copyright (c) 2025 Spotify AB. +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import * as vscode from "vscode"; +import { ChildProcess, spawn } from "child_process"; + +const SUBSYSTEM = "com.spotify.sourcekit-bazel-bsp"; +const CATEGORY = "extension"; + +export class LogStreamManager { + private process: ChildProcess | undefined; + private outputChannel: vscode.OutputChannel; + + constructor(outputChannel: vscode.OutputChannel) { + this.outputChannel = outputChannel; + } + + start(): void { + if (this.process) { + return; // Already running + } + + const predicate = `subsystem == "${SUBSYSTEM}" AND category == "${CATEGORY}"`; + const args = [ + "stream", + "--predicate", + predicate, + "--style", + "compact", + "--level", + "debug", + ]; + + this.process = spawn("log", args); + + this.process.stdout?.on("data", (data: Buffer) => { + const lines = data.toString().split("\n"); + for (const line of lines) { + const parsed = this.parseLine(line); + if (parsed) { + this.outputChannel.appendLine(parsed); + } + } + }); + + this.process.stderr?.on("data", (data: Buffer) => { + const message = data.toString().trim(); + if (message) { + this.outputChannel.appendLine(`[log stream error] ${message}`); + } + }); + + this.process.on("error", (err) => { + this.outputChannel.appendLine( + `[log stream] Failed to start: ${err.message}` + ); + this.process = undefined; + }); + + this.process.on("close", (code) => { + this.outputChannel.appendLine( + `[log stream] Process exited with code ${code}` + ); + this.process = undefined; + }); + } + + stop(): void { + if (this.process) { + this.process.kill(); + this.process = undefined; + } + } + + private parseLine(line: string): string | undefined { + const trimmed = line.trim(); + if (!trimmed) { + return undefined; + } + + // Skip log stream header lines + if ( + trimmed.startsWith("Filtering the log data") || + trimmed.startsWith("Timestamp") + ) { + return undefined; + } + + const match = trimmed.match( + /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d+\s+\S+\s+\S+\[.+?\]\s+\[.+?\]\s+(.+)$/ + ); + if (match) { + return match[1]; + } + + return trimmed; + } +} diff --git a/vscode-extension/src/logger.ts b/vscode-extension/src/logger.ts new file mode 100644 index 00000000..1918ae75 --- /dev/null +++ b/vscode-extension/src/logger.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2025 Spotify AB. +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import * as vscode from "vscode"; + +let outputChannel: vscode.OutputChannel | undefined; + +export function initLogger(): vscode.OutputChannel { + outputChannel = vscode.window.createOutputChannel("SourceKit Bazel BSP"); + return outputChannel; +} + +export function getOutputChannel(): vscode.OutputChannel | undefined { + return outputChannel; +} + +export function log(message: string) { + outputChannel?.appendLine(message); +} diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json new file mode 100644 index 00000000..fd084c46 --- /dev/null +++ b/vscode-extension/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "lib": ["ES2020"], + "outDir": "out", + "rootDir": "src", + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} From de9853e8dd002ff8c269a4c37aa7bc82cde47195 Mon Sep 17 00:00:00 2001 From: Josh Arnold Date: Sat, 10 Jan 2026 09:30:54 -0600 Subject: [PATCH 2/2] Remove extension logger setup --- .../BuildTargets/BazelTargetStore.swift | 3 - .../SKOptions/AqueryResult.swift | 3 - .../WatchedFileChangeHandler.swift | 2 - .../MessageHandler/BSPMessageHandler.swift | 5 - .../Server/SourceKitBazelBSPServer.swift | 2 - .../SharedUtils/Logger.swift | 11 +- vscode-extension/.vscode/launch.json | 20 --- vscode-extension/src/extension.ts | 22 +--- vscode-extension/src/logStream.ts | 114 ------------------ vscode-extension/src/logger.ts | 13 +- 10 files changed, 6 insertions(+), 189 deletions(-) delete mode 100644 vscode-extension/src/logStream.ts diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift b/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift index a2b42304..055ce1b1 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift @@ -181,9 +181,6 @@ final class BazelTargetStoreImpl: BazelTargetStore { logger.warning( "Target \(uri.description, privacy: .public) has multiple top-level parents; will pick the first one: \(parentToUse, privacy: .public)" ) - extensionLogger.warning( - "Target \(uri.description, privacy: .public) has multiple top-level parents; will pick the first one: \(parentToUse, privacy: .public)" - ) } let rule = try topLevelRuleType(forBazelLabel: parentToUse) let config = try topLevelConfigInfo(forBazelLabel: parentToUse) diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift b/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift index d97e4db2..146f96ab 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift @@ -39,9 +39,6 @@ struct AqueryResult: Hashable { logger.error( "Duplicate target found when aquerying (\(target.label))! This is unexpected. Will ignore the duplicate." ) - extensionLogger.error( - "Duplicate target found when aquerying (\(target.label))! This is unexpected. Will ignore the duplicate." - ) } result[target.label] = target } diff --git a/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift b/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift index c17d371b..b5cd447d 100644 --- a/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift +++ b/Sources/SourceKitBazelBSP/RequestHandlers/WatchedFiles/WatchedFileChangeHandler.swift @@ -95,7 +95,6 @@ final class WatchedFileChangeHandler { } } catch { logger.error("Error calculating deleted targets: \(error, privacy: .public)") - extensionLogger.error("Error calculating deleted targets: \(error, privacy: .public)") return [] } }() @@ -136,7 +135,6 @@ final class WatchedFileChangeHandler { } } catch { logger.error("Error calculating created targets: \(error, privacy: .public)") - extensionLogger.error("Error calculating created targets: \(error, privacy: .public)") return [] } }() diff --git a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift index 5e726154..3c764e4c 100644 --- a/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift +++ b/Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift @@ -66,7 +66,6 @@ final class BSPMessageHandler: MessageHandler { try handler(notification) } catch { logger.error("Error while handling BSP notification: \(error.localizedDescription, privacy: .public)") - extensionLogger.error("Error while handling BSP notification: \(error.localizedDescription, privacy: .public)") } } @@ -87,15 +86,11 @@ final class BSPMessageHandler: MessageHandler { logger.error( "Error while replying to \(Request.method, privacy: .public): \(error.localizedDescription, privacy: .public)" ) - extensionLogger.error( - "Error while replying to \(Request.method, privacy: .public): \(error.localizedDescription, privacy: .public)" - ) reply(.failure(buildLSPError(error))) } } } catch { logger.error("Error while handling BSP request: \(error.localizedDescription, privacy: .public)") - extensionLogger.error("Error while handling BSP request: \(error.localizedDescription, privacy: .public)") reply(.failure(buildLSPError(from: error))) } } diff --git a/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift b/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift index 0664d67c..7984e4b4 100644 --- a/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift +++ b/Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift @@ -129,7 +129,6 @@ package final class SourceKitBazelBSPServer { /// we get a shutdown request from sourcekit-lsp. package func run(parkThread: Bool = true) { logger.info("Connecting to sourcekit-lsp...") - extensionLogger.notice("BSP server starting...") connection.start( receiveHandler: handler, @@ -145,7 +144,6 @@ package final class SourceKitBazelBSPServer { } logger.info("Connection established, parking thread.") - extensionLogger.notice("BSP server connected to sourcekit-lsp ✓") // Park the thread by sleeping for 10 years. // All request handling is done on other threads and sourcekit-bazel-bsp exits by calling `_Exit` when it receives a diff --git a/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift b/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift index 84a36ee0..54930a1e 100644 --- a/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift +++ b/Sources/SourceKitBazelBSP/SharedUtils/Logger.swift @@ -20,16 +20,7 @@ import Foundation import OSLog -private let bspSubsystem = "com.spotify.sourcekit-bazel-bsp" - /// Simple helper to create loggers under the `com.spotify.sourcekit-bazel-bsp` subsystem. package func makeFileLevelBSPLogger(withCategory category: String = #fileID) -> Logger { - Logger(subsystem: bspSubsystem, category: category) + Logger(subsystem: "com.spotify.sourcekit-bazel-bsp", category: category) } - -/// Logger specifically for messages intended to be shown in the VSCode extension. -/// Use this for user-facing status updates, not internal debugging. -/// -/// The extension filters for this category using: -/// `log stream --process sourcekit-bazel-bsp --predicate 'category == "extension"'` -package let extensionLogger = Logger(subsystem: bspSubsystem, category: "extension") diff --git a/vscode-extension/.vscode/launch.json b/vscode-extension/.vscode/launch.json index d58eca55..97e7a593 100644 --- a/vscode-extension/.vscode/launch.json +++ b/vscode-extension/.vscode/launch.json @@ -8,26 +8,6 @@ "args": ["--extensionDevelopmentPath=${workspaceFolder}"], "outFiles": ["${workspaceFolder}/out/**/*.js"], "preLaunchTask": "npm: compile" - }, - { - "type": "swift", - "request": "launch", - "args": [], - "cwd": "${workspaceFolder:vscode-extension}", - "name": "Debug sourcekit-bazel-bsp", - "target": "sourcekit-bazel-bsp", - "configuration": "debug", - "preLaunchTask": "swift: Build Debug sourcekit-bazel-bsp" - }, - { - "type": "swift", - "request": "launch", - "args": [], - "cwd": "${workspaceFolder:vscode-extension}", - "name": "Release sourcekit-bazel-bsp", - "target": "sourcekit-bazel-bsp", - "configuration": "release", - "preLaunchTask": "swift: Build Release sourcekit-bazel-bsp" } ] } diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 9931bc11..e59c8f2f 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -17,28 +17,12 @@ // specific language governing permissions and limitations // under the License. -import * as vscode from "vscode"; -import { initLogger, log } from "./logger"; -import { LogStreamManager } from "./logStream"; +import { log } from "./logger"; -let logStreamManager: LogStreamManager | undefined; - -export function activate(context: vscode.ExtensionContext) { - const outputChannel = initLogger(); +export function activate() { log("Extension activated ✅"); - - // Start capturing BSP server logs - logStreamManager = new LogStreamManager(outputChannel); - logStreamManager.start(); - - context.subscriptions.push({ - dispose: () => { - logStreamManager?.stop(); - }, - }); } export function deactivate() { - logStreamManager?.stop(); - logStreamManager = undefined; + log("Extension deactivated ❌"); } diff --git a/vscode-extension/src/logStream.ts b/vscode-extension/src/logStream.ts deleted file mode 100644 index 5b44bad4..00000000 --- a/vscode-extension/src/logStream.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2025 Spotify AB. -// -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -import * as vscode from "vscode"; -import { ChildProcess, spawn } from "child_process"; - -const SUBSYSTEM = "com.spotify.sourcekit-bazel-bsp"; -const CATEGORY = "extension"; - -export class LogStreamManager { - private process: ChildProcess | undefined; - private outputChannel: vscode.OutputChannel; - - constructor(outputChannel: vscode.OutputChannel) { - this.outputChannel = outputChannel; - } - - start(): void { - if (this.process) { - return; // Already running - } - - const predicate = `subsystem == "${SUBSYSTEM}" AND category == "${CATEGORY}"`; - const args = [ - "stream", - "--predicate", - predicate, - "--style", - "compact", - "--level", - "debug", - ]; - - this.process = spawn("log", args); - - this.process.stdout?.on("data", (data: Buffer) => { - const lines = data.toString().split("\n"); - for (const line of lines) { - const parsed = this.parseLine(line); - if (parsed) { - this.outputChannel.appendLine(parsed); - } - } - }); - - this.process.stderr?.on("data", (data: Buffer) => { - const message = data.toString().trim(); - if (message) { - this.outputChannel.appendLine(`[log stream error] ${message}`); - } - }); - - this.process.on("error", (err) => { - this.outputChannel.appendLine( - `[log stream] Failed to start: ${err.message}` - ); - this.process = undefined; - }); - - this.process.on("close", (code) => { - this.outputChannel.appendLine( - `[log stream] Process exited with code ${code}` - ); - this.process = undefined; - }); - } - - stop(): void { - if (this.process) { - this.process.kill(); - this.process = undefined; - } - } - - private parseLine(line: string): string | undefined { - const trimmed = line.trim(); - if (!trimmed) { - return undefined; - } - - // Skip log stream header lines - if ( - trimmed.startsWith("Filtering the log data") || - trimmed.startsWith("Timestamp") - ) { - return undefined; - } - - const match = trimmed.match( - /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d+\s+\S+\s+\S+\[.+?\]\s+\[.+?\]\s+(.+)$/ - ); - if (match) { - return match[1]; - } - - return trimmed; - } -} diff --git a/vscode-extension/src/logger.ts b/vscode-extension/src/logger.ts index 1918ae75..b800f7c2 100644 --- a/vscode-extension/src/logger.ts +++ b/vscode-extension/src/logger.ts @@ -19,17 +19,8 @@ import * as vscode from "vscode"; -let outputChannel: vscode.OutputChannel | undefined; - -export function initLogger(): vscode.OutputChannel { - outputChannel = vscode.window.createOutputChannel("SourceKit Bazel BSP"); - return outputChannel; -} - -export function getOutputChannel(): vscode.OutputChannel | undefined { - return outputChannel; -} +const outputChannel = vscode.window.createOutputChannel("SourceKit Bazel BSP"); export function log(message: string) { - outputChannel?.appendLine(message); + outputChannel.appendLine(message); }