From aac3d0e97df05debbda208e05ea53dea3213721b Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Fri, 10 Dec 2021 11:50:51 -0700 Subject: [PATCH 1/4] Initial gopls support --- package-lock.json | 41 +++++- package.json | 28 +++- src/bezel/configuration.ts | 36 ++++- src/bezel/constants.ts | 1 + src/bezel/download.ts | 9 +- src/bezel/feature.ts | 14 +- src/bezel/workspaceView.ts | 38 +++++- src/container.ts | 4 + src/golang/configuration.ts | 22 ++++ src/golang/golang.ts | 71 ++++++++++ src/golang/gopackagesdriver/BUILD.bazel | 0 src/golang/gopackagesdriver/WORKSPACE | 0 src/golang/gopackagesdriver/aspect.bzl | 166 ++++++++++++++++++++++++ src/golang/gopls.ts | 61 +++++++++ src/golang/settings.ts | 108 +++++++++++++++ 15 files changed, 586 insertions(+), 13 deletions(-) create mode 100644 src/golang/configuration.ts create mode 100644 src/golang/golang.ts create mode 100644 src/golang/gopackagesdriver/BUILD.bazel create mode 100644 src/golang/gopackagesdriver/WORKSPACE create mode 100644 src/golang/gopackagesdriver/aspect.bzl create mode 100644 src/golang/gopls.ts create mode 100644 src/golang/settings.ts diff --git a/package-lock.json b/package-lock.json index 62dca276..3c8edf06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "bazel-stack-vscode", - "version": "1.7.0", + "version": "1.8.3", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.7.0", + "version": "1.8.3", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.3.4", @@ -15,6 +15,7 @@ "bazel-stack-vscode-api": "^1.2.2", "find-up": "^5.0.0", "fs-extra": "9.0.1", + "get-port": "^5.1.1", "graceful-fs": "4.2.4", "luxon": "1.24.1", "mv": "^2.1.1", @@ -39,6 +40,7 @@ "@types/chai-string": "1.4.2", "@types/find-up": "^4.0.0", "@types/fs-extra": "9.0.1", + "@types/get-port": "^4.2.0", "@types/glob": "^7.1.1", "@types/graceful-fs": "4.1.2", "@types/luxon": "1.24.3", @@ -481,6 +483,16 @@ "@types/node": "*" } }, + "node_modules/@types/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-Iv2FAb5RnIk/eFO2CTu8k+0VMmIR15pKbcqRWi+s3ydW+aKXlN2yemP92SrO++ERyJx+p6Ie1ggbLBMbU1SjiQ==", + "deprecated": "This is a stub types definition. get-port provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "get-port": "*" + } + }, "node_modules/@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -2164,6 +2176,17 @@ "has-symbols": "^1.0.1" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -5323,6 +5346,15 @@ "@types/node": "*" } }, + "@types/get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-Iv2FAb5RnIk/eFO2CTu8k+0VMmIR15pKbcqRWi+s3ydW+aKXlN2yemP92SrO++ERyJx+p6Ie1ggbLBMbU1SjiQ==", + "dev": true, + "requires": { + "get-port": "*" + } + }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -6728,6 +6760,11 @@ "has-symbols": "^1.0.1" } }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==" + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", diff --git a/package.json b/package.json index 902ffee1..0bd30133 100644 --- a/package.json +++ b/package.json @@ -467,6 +467,24 @@ }, "default": [] }, + "bsv.golang.enabled": { + "type": "boolean", + "description": "If false, disable the golang component", + "default": true + }, + "bsv.golang.gopackagesdriver.release": { + "type": "string", + "default": "v1.3.21", + "description": "The release tag of the gopackagesdriver executable" + }, + "bsv.golang.gopackagesdriver.flags": { + "type": "array", + "description": "optional flags for the gopackagesdriver executable", + "items": { + "type": "string" + }, + "default": [] + }, "bsv.bzldoc.base-url": { "type": "string", "default": "https://docs.bazel.build/versions/master", @@ -494,6 +512,12 @@ "title": "Buildozer: Run Command Wizard", "icon": "$(zap)" }, + { + "category": "Bzl", + "command": "bsv.golang.gopls.wizard", + "title": "Golang: Configure gopls", + "icon": "$(zap)" + }, { "category": "Bzl", "command": "bsv.bzl.redo", @@ -772,6 +796,7 @@ "bazel-stack-vscode-api": "^1.2.2", "find-up": "^5.0.0", "fs-extra": "9.0.1", + "get-port": "^5.1.1", "graceful-fs": "4.2.4", "luxon": "1.24.1", "mv": "^2.1.1", @@ -796,6 +821,7 @@ "@types/chai-string": "1.4.2", "@types/find-up": "^4.0.0", "@types/fs-extra": "9.0.1", + "@types/get-port": "^4.2.0", "@types/glob": "^7.1.1", "@types/graceful-fs": "4.1.2", "@types/luxon": "1.24.3", @@ -831,4 +857,4 @@ "tabWidth": 2, "arrowParens": "avoid" } -} \ No newline at end of file +} diff --git a/src/bezel/configuration.ts b/src/bezel/configuration.ts index 8405d5ff..79943bb5 100644 --- a/src/bezel/configuration.ts +++ b/src/bezel/configuration.ts @@ -9,6 +9,8 @@ import { ProtoGrpcType as BzlProtoType } from '../proto/bzl'; import { ProtoGrpcType as CodesearchProtoType } from '../proto/codesearch'; import { getGRPCCredentials, loadBzlProtos, loadCodesearchProtos } from './proto'; import { ConfigurationContext } from '../common'; +import getPort = require('get-port'); +import { Container } from '../container'; /** * Configuration for a generic component. @@ -131,8 +133,16 @@ export interface LanguageServerConfiguration extends ComponentConfiguration { enableCodelensTest: boolean; // enable run codelens enableCodelensRun: boolean; + // address for gopackagesdriver server + gopackagesdriver: GopackagesdriverServerConfiguration, } +export interface GopackagesdriverServerConfiguration { + host: string; + port: number; +} + + export interface StarlarkDebuggerConfiguration extends ComponentConfiguration { autoLaunch: boolean; debugAdapterExecutable: string, @@ -249,6 +259,7 @@ export class BzlSettings extends Settings { protected async configure(config: vscode.WorkspaceConfiguration): Promise { const bazel = await this.bazel.get(); const address = vscode.Uri.parse(config.get('address', 'grpc://localhost:8085')); + const cfg: BzlConfiguration = { enabled: config.get('enabled', true), autoLaunch: config.get('autoLaunch', true), @@ -362,10 +373,19 @@ export class LanguageServerSettings extends Settings { const bzl = await this.bzl.get(); + const gopackagesdriver: GopackagesdriverServerConfiguration = { + host: 'localhost', + port: await getPort({ port: 10022 }), + }; + const cfg: LanguageServerConfiguration = { enabled: config.get('enabled', true), executable: bzl.executable, - command: config.get('command', ['lsp', 'serve', '--log_level=info']), + command: config.get('command', [ + 'lsp', + 'serve', + '--log_level=info', + ]), enableCodelenses: config.get('enableCodelenses', true), enableCodelensCopyLabel: config.get('enableCodelensCopyLabel', true), enableCodelensCodesearch: config.get('enableCodelensCodesearch', true), @@ -374,9 +394,13 @@ export class LanguageServerSettings extends Settings('enableCodelensBuild', true), enableCodelensTest: config.get('enableCodelensTest', true), enableCodelensRun: config.get('enableCodelensRun', true), + gopackagesdriver: gopackagesdriver, }; cfg.command.push(`--address=${bzl.address}`); + cfg.command.push(`--gopackagesdriver_address=${gopackagesdriver.host}:${gopackagesdriver.port}`); + cfg.command.push(`--gopackagesdriver_aspect_label=@gopackagesdriver//:aspect.bzl`); + cfg.command.push(`--gopackagesdriver_build_flags=--override_repository=gopackagesdriver=${Container.file('src', 'golang', 'gopackagesdriver').fsPath}`); const subscription = await this.subscription.get(); if (!subscription.token) { @@ -434,8 +458,8 @@ export async function setServerExecutable( } /** - * Installs bzl. If the expected file already exists the - * download operation is skipped. + * Installs bzl. If the expected file already exists the download operation is + * skipped. * * @param cfg The configuration * @param storagePath The directory where the binary should be installed @@ -446,7 +470,11 @@ export async function maybeInstallExecutable( ): Promise { const cancellationTokenSource = new vscode.CancellationTokenSource(); const cancellationToken = cancellationTokenSource.token; - const downloader = await BzlAssetDownloader.fromConfiguration(cfg); + const downloader = await BzlAssetDownloader.fromConfiguration({ + basename: 'bzl', + downloadBaseURL: cfg.downloadBaseURL, + release: cfg.release, + }); const mode = 0o755; return downloader.getOrDownloadFile(ctx, mode, cancellationToken); } diff --git a/src/bezel/constants.ts b/src/bezel/constants.ts index 4e63108f..116c9bbb 100644 --- a/src/bezel/constants.ts +++ b/src/bezel/constants.ts @@ -52,6 +52,7 @@ export enum CommandName { DebugBuild = 'bsv.bzl.debugBuild', AskForDebugTargetLabel = 'bsv.bzl.askForDebugTargetLabel', BuildozerWizard = 'bsv.buildozer.wizard', + GoplsWizard = 'bsv.golang.gopls.wizard', LaunchDebugAdapter = 'bsv.bzl.starlarkDebugger.launch', LaunchRemoteCache = 'bsv.bzl.remoteCache.launch', LaunchBzlServer = 'bsv.bzl.server.launch', diff --git a/src/bezel/download.ts b/src/bezel/download.ts index 86ebb4d8..f132a035 100644 --- a/src/bezel/download.ts +++ b/src/bezel/download.ts @@ -8,6 +8,11 @@ import { getApi, FileDownloader } from '@microsoft/vscode-file-downloader-api'; * Configuration type that describes a desired asset from bzl.io. */ export interface BzlAssetConfiguration { + /** + * The basename of the binary, like 'bzl;. + */ + basename: string; + /** * The base URL (e.g "https://bzl.io"). */ @@ -20,10 +25,10 @@ export interface BzlAssetConfiguration { } export class BzlAssetDownloader { - private constructor(private downloaderApi: FileDownloader, private cfg: BzlAssetConfiguration) {} + private constructor(private downloaderApi: FileDownloader, private cfg: BzlAssetConfiguration) { } getBasename(): string { - let basename = 'bzl'; + let basename = this.cfg.basename; if (process.platform === 'win32') { basename += '.exe'; } diff --git a/src/bezel/feature.ts b/src/bezel/feature.ts index 4f5611a1..c3006cb9 100644 --- a/src/bezel/feature.ts +++ b/src/bezel/feature.ts @@ -34,6 +34,8 @@ import { ConfigurationContext } from '../common'; import findUp = require('find-up'); import path = require('path'); import { Buildozer } from '../buildozer/buildozer'; +import { GolangSettings } from '../golang/settings'; +import { Golang } from '../golang/golang'; export const BzlFeatureName = 'bsv.bzl'; @@ -67,8 +69,10 @@ export class BzlFeature implements vscode.Disposable { constructor(private api: API, ctx: vscode.ExtensionContext, private configCtx: ConfigurationContext) { let cwd = "." + let workspace: vscode.WorkspaceFolder | undefined; if (vscode.workspace.workspaceFolders?.length) { - cwd = vscode.workspace.workspaceFolders[0].uri.fsPath; + workspace = vscode.workspace.workspaceFolders[0]; + cwd = workspace.uri.fsPath; } const workspaceFolder = findWorkspaceFolder(cwd); @@ -119,6 +123,10 @@ export class BzlFeature implements vscode.Disposable { new LanguageServerSettings(configCtx, 'bsv.bzl.lsp', bzlSettings, subscriptionSettings) ); + const golangSettings = this.addDisposable( + new GolangSettings(configCtx, 'bsv.golang', ctx, bzlSettings, languageServerSettings) + ); + // ======= Components ========= const subscription = this.addComponent(new Subscription(subscriptionSettings, bzlSettings)); @@ -150,6 +158,7 @@ export class BzlFeature implements vscode.Disposable { new StarlarkDebugger(debugSettings, bazelSettings, bzlSettings, workspaceFolder) )); const codeSearch = this.addComponent(new CodeSearch(codeSearchSettings, bzl)); + const golang = this.addComponent(new Golang(golangSettings, ctx.storageUri!, workspace, bazelServer)); this.addDisposable( new BezelWorkspaceView( lspClient, @@ -162,7 +171,8 @@ export class BzlFeature implements vscode.Disposable { bazelServer, starlarkDebugger, codeSearch, - invocations + invocations, + golang, ) ); } diff --git a/src/bezel/workspaceView.ts b/src/bezel/workspaceView.ts index 806dabac..b9a680ce 100644 --- a/src/bezel/workspaceView.ts +++ b/src/bezel/workspaceView.ts @@ -38,6 +38,8 @@ import { CodeSearch } from './codesearch'; import { Invocations, InvocationsItem } from './invocations'; import { Buildozer } from '../buildozer/buildozer'; import { BuildozerConfiguration } from '../buildozer/configuration'; +import { GolangConfiguration } from '../golang/configuration'; +import { Golang } from '../golang/golang'; export interface Expandable { getChildren(): Promise; @@ -62,6 +64,7 @@ export class BezelWorkspaceView extends TreeView { private bazelServerItem: BazelServerItem; private codeSearchItem: CodeSearchItem; private invocationsItem: InvocationsItem; + private golangItem: GolangItem; constructor( public readonly lspClient: BzlLanguageClient, @@ -74,7 +77,8 @@ export class BezelWorkspaceView extends TreeView { private bazel: BazelServer, starlarkDebugger: StarlarkDebugger, codeSearch: CodeSearch, - invocations: Invocations + invocations: Invocations, + golang: Golang, ) { super(ViewName.Workspace); @@ -83,6 +87,8 @@ export class BezelWorkspaceView extends TreeView { this.view.reveal(item); }; + this.golangItem = this.addDisposable(new GolangItem(golang, onDidChangeTreeData)); + this.buildifierItem = this.addDisposable(new BuildifierItem(buildifier, onDidChangeTreeData)); this.buildozerItem = this.addDisposable(new BuildozerItem(buildozer, onDidChangeTreeData)); this.remoteCacheItem = this.addDisposable( @@ -201,6 +207,7 @@ export class BezelWorkspaceView extends TreeView { protected async getRootItems(): Promise { const items: vscode.TreeItem[] = [ + this.golangItem, this.buildifierItem, this.buildozerItem, this.starlarkDebuggerItem, @@ -510,6 +517,34 @@ class BuildifierItem } } +class GolangItem + extends RunnableComponentItem + implements vscode.Disposable, Expandable { + constructor(golang: Golang, onDidChangeTreeData: (item: vscode.TreeItem) => void) { + super('Go', 'Language', golang, onDidChangeTreeData); + this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; + } + + async getChildrenInternal(): Promise { + const items: vscode.TreeItem[] = []; + items.push(new DocumentationLinkItem('golang')) + items.push(this.createRunWizardItem()); + return items; + } + + createRunWizardItem(): vscode.TreeItem { + const item = new vscode.TreeItem('Configure gopls'); + item.description = 'Command Helper'; + item.iconPath = new vscode.ThemeIcon('zap'); + item.command = { + title: 'Run Gopls Wizard', + command: CommandName.GoplsWizard, + }; + return item; + } + +} + class BuildozerItem extends RunnableComponentItem implements vscode.Disposable, Expandable { @@ -535,7 +570,6 @@ class BuildozerItem }; return item; } - } class RemoteCacheItem diff --git a/src/container.ts b/src/container.ts index 6ac93930..d803b512 100644 --- a/src/container.ts +++ b/src/container.ts @@ -24,6 +24,10 @@ export class Container { return Container._telemetry; } + static file(...names: string[]): vscode.Uri { + return vscode.Uri.file(path.join(this._configCtx.extensionUri.fsPath, ...names)); + } + static media(name: MediaIconName): vscode.Uri { return vscode.Uri.file(path.join(this._configCtx.extensionUri.fsPath, 'media', name)); } diff --git a/src/golang/configuration.ts b/src/golang/configuration.ts new file mode 100644 index 00000000..8752df42 --- /dev/null +++ b/src/golang/configuration.ts @@ -0,0 +1,22 @@ +import { ComponentConfiguration } from '../bezel/configuration'; + +/** + * Configuration for the golang module. + */ +export interface GolangConfiguration extends ComponentConfiguration { + gopackagesdriver: GopackagesdriverClientConfiguration; +} + +/** + * Configuration for the gopackagesdriver. + */ +export interface GopackagesdriverClientConfiguration { + // The release of gopackagesdriver + release: string; + // Path to gopackagesdriver frontend + executable: string; + // Path to gopackagesdriver entrypoint script + script: string; + // optional flags for gopackagesdriver + flags: string[] | undefined; +} diff --git a/src/golang/golang.ts b/src/golang/golang.ts new file mode 100644 index 00000000..cb43d11c --- /dev/null +++ b/src/golang/golang.ts @@ -0,0 +1,71 @@ +import * as vscode from 'vscode'; + +import { GolangConfiguration } from './configuration'; +import { GolangSettings } from './settings'; +import { RunnableComponent } from '../bezel/status'; +import { CommandName } from '../bezel/constants'; +import { GoplsWizard } from './gopls'; +import { MultiStepInput } from '../multiStepInput'; +import { BazelServer } from '../bezel/bazel'; + +export class Golang extends RunnableComponent { + constructor( + public readonly settings: GolangSettings, + private storageUri: vscode.Uri, + private workspaceFolder: vscode.WorkspaceFolder | undefined, + private bazel: BazelServer, + ) { + super('GOL', settings); + + this.addCommand(CommandName.GoplsWizard, this.handleGoplsWizardCommand); + } + + async handleGoplsWizardCommand() { + const config = await this.settings.get(); + const info = await this.bazel.getBazelInfo(); + + const go = vscode.workspace.getConfiguration('go', this.workspaceFolder); + + // ---- before --- + + let goroot = go.get('goroot', '${output_base}/external/go_sdk'); + let toolsEnvVars = go.get<{ [key: string]: string }>('toolsEnvVars', {}) + + // go.get can actually return undefined? + if (!goroot) { + goroot = ''; + } + if (info?.outputBase) { + goroot = goroot.replace(info?.outputBase, '${output_base}'); + } + + const wizard = new GoplsWizard(new MultiStepInput(vscode.window)); + wizard.goroot = go.get('goroot', '${output_base}/external/go_sdk'); + + // ---- run --- + + await wizard.run(); + + // ---- after --- + + if (wizard.goroot) { + goroot = wizard.goroot.replace('${output_base}', info?.outputBase!); + await go.update('goroot', goroot); + } + + await go.update('useLanguageServer', wizard.useLanguageServer); + + // ---- go.toolsEnvVars --- + + if (config.gopackagesdriver.script) { + toolsEnvVars["GOPACKAGESDRIVER"] = config.gopackagesdriver.script; + await go.update('toolsEnvVars', toolsEnvVars); + } + + } + + async startInternal(): Promise { + } + + async stopInternal(): Promise { } +} diff --git a/src/golang/gopackagesdriver/BUILD.bazel b/src/golang/gopackagesdriver/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/src/golang/gopackagesdriver/WORKSPACE b/src/golang/gopackagesdriver/WORKSPACE new file mode 100644 index 00000000..e69de29b diff --git a/src/golang/gopackagesdriver/aspect.bzl b/src/golang/gopackagesdriver/aspect.bzl new file mode 100644 index 00000000..a37bae28 --- /dev/null +++ b/src/golang/gopackagesdriver/aspect.bzl @@ -0,0 +1,166 @@ +"""aspect.bzl gathers go.pkg.json files +""" + +# Copyright 2021 The Bazel Go Rules Authors. All rights reserved. +# +# Licensed 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. + +load( + "@io_bazel_rules_go//go/private:providers.bzl", + "GoArchive", +) +load( + "@bazel_skylib//lib:paths.bzl", + "paths", +) + +# GoPkgInfo = provider(doc = "Info about a go package", fields = {}) +GoPkgInfo = provider() + +def _is_file_external(f): + return f.owner.workspace_root != "" + +def _file_path(f): + prefix = "__BAZEL_WORKSPACE__" + if not f.is_source: + prefix = "__BAZEL_EXECROOT__" + elif _is_file_external(f): + prefix = "__BAZEL_OUTPUT_BASE__" + return paths.join(prefix, f.path) + +def _go_archive_to_pkg(archive): + return struct( + ID = str(archive.data.label), + PkgPath = archive.data.importpath, + ExportFile = _file_path(archive.data.export_file), + GoFiles = [ + _file_path(src) + for src in archive.data.orig_srcs + ], + CompiledGoFiles = [ + _file_path(src) + for src in archive.data.srcs + ], + ) + +def _make_pkg_json(ctx, archive, pkg_info): + pkg_json_file = ctx.actions.declare_file(archive.data.name + ".pkg.json") + ctx.actions.write(pkg_json_file, content = pkg_info.to_json()) + return pkg_json_file + +def _go_pkg_info_aspect_impl(target, ctx): + # Fetch the stdlib JSON file from the inner most target + # stdlib_json_file = None + + deps_transitive_json_file = [] + deps_transitive_export_file = [] + deps_transitive_compiled_go_files = [] + + for attr in ["deps", "embed"]: + for dep in getattr(ctx.rule.attr, attr, []): + if GoPkgInfo in dep: + pkg_info = dep[GoPkgInfo] + if attr == "deps": + deps_transitive_json_file.append(pkg_info.transitive_json_file) + deps_transitive_export_file.append(pkg_info.transitive_export_file) + deps_transitive_compiled_go_files.append(pkg_info.transitive_compiled_go_files) + elif attr == "embed": + # If deps are embedded, do not gather their json or export_file since they + # are included in the current target, but do gather their deps'. + deps_transitive_json_file.append(pkg_info.deps_transitive_json_file) + deps_transitive_export_file.append(pkg_info.deps_transitive_export_file) + deps_transitive_compiled_go_files.append(pkg_info.deps_transitive_compiled_go_files) + + # Fetch the stdlib json from the first dependency + # if not stdlib_json_file: + # stdlib_json_file = pkg_info.stdlib_json_file + + pkg_json_files = [] + compiled_go_files = [] + export_files = [] + + if GoArchive in target: + archive = target[GoArchive] + compiled_go_files.extend(archive.source.srcs) + export_files.append(archive.data.export_file) + pkg = _go_archive_to_pkg(archive) + pkg_json_files.append(_make_pkg_json(ctx, archive, pkg)) + + # if the rule is a test, we need to get the embedded go_library with the current + # test's sources. For that, consume the dependency via GoArchive.direct so that + # the test source files are there. Then, create the pkg json file directly. Only + # do that for direct dependencies that are not defined as deps, and use the + # importpath to find which. + if ctx.rule.kind == "go_test": + deps_targets = [ + dep[GoArchive].data.importpath + for dep in ctx.rule.attr.deps + if GoArchive in dep + ] + for archive in target[GoArchive].direct: + if archive.data.importpath not in deps_targets: + pkg = _go_archive_to_pkg(archive) + pkg_json_files.append(_make_pkg_json(ctx, archive, pkg)) + compiled_go_files.extend(archive.source.srcs) + export_files.append(archive.data.export_file) + + # If there was no stdlib json in any dependencies, fetch it from the + # current go_ node. + # if not stdlib_json_file: + # stdlib_json_file = ctx.attr._go_stdlib[GoStdLib]._list_json + + pkg_info = GoPkgInfo( + # stdlib_json_file = stdlib_json_file, + transitive_json_file = depset( + direct = pkg_json_files, + transitive = deps_transitive_json_file, + ), + deps_transitive_json_file = depset( + transitive = deps_transitive_json_file, + ), + transitive_compiled_go_files = depset( + direct = compiled_go_files, + transitive = deps_transitive_compiled_go_files, + ), + deps_transitive_compiled_go_files = depset( + transitive = deps_transitive_compiled_go_files, + ), + transitive_export_file = depset( + direct = export_files, + transitive = deps_transitive_export_file, + ), + deps_transitive_export_file = depset( + transitive = deps_transitive_export_file, + ), + ) + + return [ + pkg_info, + OutputGroupInfo( + go_pkg_driver_json_file = pkg_info.transitive_json_file, + go_pkg_driver_srcs = pkg_info.transitive_compiled_go_files, + go_pkg_driver_export_file = pkg_info.transitive_export_file, + # go_pkg_driver_stdlib_json_file = depset([pkg_info.stdlib_json_file] if pkg_info.stdlib_json_file else []), + go_pkg_driver_stdlib_json_file = depset([]), + ), + ] + +go_pkg_info_aspect = aspect( + implementation = _go_pkg_info_aspect_impl, + attr_aspects = ["embed", "deps"], + attrs = { + "_go_stdlib": attr.label( + default = "@io_bazel_rules_go//:stdlib", + ), + }, +) diff --git a/src/golang/gopls.ts b/src/golang/gopls.ts new file mode 100644 index 00000000..cb0737b4 --- /dev/null +++ b/src/golang/gopls.ts @@ -0,0 +1,61 @@ +import { InputStep, MultiStepInput } from '../multiStepInput'; +import { ThemeIconCheck, ThemeIconClose } from '../bezel/constants'; + +export class GoplsWizard { + private totalSteps: number = 3; + private currentStep: number = 1; + private targets: string[] = []; + private command: string = ''; + private commandArgs: string[] = []; + + constructor( + private readonly input: MultiStepInput, + public goroot: string = '${output_base}/external/go_sdk', + public useLanguageServer: boolean = true, + ) { + } + + getTargets(): string[] { + return this.targets; + } + + getCommand(): string { + return [this.command, ...this.commandArgs].join(' '); + } + + async run(): Promise { + return this.input.stepThrough(this.pickUseGopls.bind(this)); + } + + async pickGoroot(input: MultiStepInput): Promise { + const wd = await input.showInputBox({ + title: 'gopls: GOROOT', + totalSteps: this.totalSteps, + step: this.currentStep, + value: this.goroot, + prompt: 'Location of GOROOT', + validate: async (value: string) => { return ''; }, + shouldResume: async () => false, + }); + this.goroot = wd; + return; + } + + async pickUseGopls(input: MultiStepInput): Promise { + const picked = await input.showQuickPick({ + title: 'gopls', + totalSteps: this.totalSteps, + step: this.currentStep, + items: [{ label: 'yes' }, { label: 'no' }], + placeholder: 'Use the gopls language server?', + validate: async (value: string) => { return value == 'yes' || value == 'no'; }, + shouldResume: async () => false, + }); + this.useLanguageServer = picked.label === 'yes'; + if (!this.useLanguageServer) { + return; + } + return this.pickGoroot(input); + } + +} diff --git a/src/golang/settings.ts b/src/golang/settings.ts new file mode 100644 index 00000000..1b8089b9 --- /dev/null +++ b/src/golang/settings.ts @@ -0,0 +1,108 @@ +import * as vscode from 'vscode'; +import * as fs from 'graceful-fs'; +import path = require('path'); +import fsExtra = require('fs-extra'); +import normalize = require('normalize-path'); +import { Settings } from '../bezel/settings'; +import { GolangConfiguration, GopackagesdriverClientConfiguration } from './configuration'; +import { ConfigurationContext } from '../common'; +import { BzlAssetDownloader } from '../bezel/download'; +import { BzlSettings, GopackagesdriverServerConfiguration, LanguageServerSettings } from '../bezel/configuration'; + +export class GolangSettings extends Settings { + constructor( + configCtx: ConfigurationContext, + section: string, + private ctx: vscode.ExtensionContext, + private bzlSettings: BzlSettings, + private lspSettings: LanguageServerSettings, + ) { + super(configCtx, section); + } + + protected async configure( + config: vscode.WorkspaceConfiguration + ): Promise { + const cfg: GolangConfiguration = { + enabled: config.get('enabled', true), + gopackagesdriver: { + release: config.get('gopackagesdriver.release', 'v1.3.21'), + executable: normalize(config.get('gopackagesdriver.executable', '')), + flags: config.get('gopackagesdriver.flags'), + script: `${path.join(this.ctx.storageUri?.fsPath!, 'gopackagesdriver')}`, + }, + }; + + if (!cfg.gopackagesdriver.executable) { + const bzl = await this.bzlSettings.get(); + await setServerExecutable(this.ctx, bzl.downloadBaseURL, cfg.gopackagesdriver); + } + + const lsp = await this.lspSettings.get(); + installGopackagesDriverScript(lsp.gopackagesdriver, cfg.gopackagesdriver); + + return cfg; + } +} + +async function setServerExecutable( + ctx: vscode.ExtensionContext, + downloadBaseURL: string, + gopackagesdriver: GopackagesdriverClientConfiguration, +): Promise { + try { + const fileUri = await maybeInstallExecutable(ctx, downloadBaseURL, gopackagesdriver.release); + gopackagesdriver.executable = normalize(fileUri.fsPath); + } catch (e) { + throw new Error(`could not install gopackagesdriver: ${e instanceof Error ? e.message : e}`); + } + if (!fs.existsSync(gopackagesdriver.executable)) { + throw new Error(`could not activate: gopackagesdriver file "${gopackagesdriver.executable}" not found.`); + } +} + +/** + * Installs gopackagesdriver. If the expected file already exists the download + * operation is skipped. + */ +async function maybeInstallExecutable( + ctx: vscode.ExtensionContext, + downloadBaseURL: string, + release: string, +): Promise { + const cancellationTokenSource = new vscode.CancellationTokenSource(); + const cancellationToken = cancellationTokenSource.token; + const downloader = await BzlAssetDownloader.fromConfiguration({ + basename: 'gopackagesdriver', + downloadBaseURL: downloadBaseURL, + release: release, + }); + const mode = 0o755; + return downloader.getOrDownloadFile(ctx, mode, cancellationToken); +} + +async function installGopackagesDriverScript( + server: GopackagesdriverServerConfiguration, + client: GopackagesdriverClientConfiguration, +) { + + try { + fsExtra.ensureDirSync(path.dirname(client.script), { + mode: 0o755, + }); + + fsExtra.writeFileSync(client.script, [ + "#!/usr/bin/env bash", + `exec '${client.executable}' --log_level=debug --log_file=/tmp/gpdc2.log --server_host=${server.host} --server_port=${server.port} "\${@}"`, + "", + ].join("\n"), { + mode: 0o755, + }); + } catch (e) { + if (e instanceof Error) { + vscode.window.showErrorMessage(`failed to write client script: ${e.message}`); + } + client.script = ''; // set to empty string to disable this functionality for now + } + +} \ No newline at end of file From d3598d5ad69bc0cce6d0ba33dc89b0684bedb5cd Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Mon, 13 Dec 2021 09:18:32 -0700 Subject: [PATCH 2/4] stdlib cleanup --- src/golang/gopackagesdriver/aspect.bzl | 50 ++++++++------------------ 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/golang/gopackagesdriver/aspect.bzl b/src/golang/gopackagesdriver/aspect.bzl index a37bae28..c4c8b3ec 100644 --- a/src/golang/gopackagesdriver/aspect.bzl +++ b/src/golang/gopackagesdriver/aspect.bzl @@ -3,17 +3,17 @@ # Copyright 2021 The Bazel Go Rules Authors. All rights reserved. # -# Licensed 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 +# Licensed 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. +# 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. load( "@io_bazel_rules_go//go/private:providers.bzl", @@ -24,7 +24,6 @@ load( "paths", ) -# GoPkgInfo = provider(doc = "Info about a go package", fields = {}) GoPkgInfo = provider() def _is_file_external(f): @@ -59,9 +58,6 @@ def _make_pkg_json(ctx, archive, pkg_info): return pkg_json_file def _go_pkg_info_aspect_impl(target, ctx): - # Fetch the stdlib JSON file from the inner most target - # stdlib_json_file = None - deps_transitive_json_file = [] deps_transitive_export_file = [] deps_transitive_compiled_go_files = [] @@ -75,16 +71,13 @@ def _go_pkg_info_aspect_impl(target, ctx): deps_transitive_export_file.append(pkg_info.transitive_export_file) deps_transitive_compiled_go_files.append(pkg_info.transitive_compiled_go_files) elif attr == "embed": - # If deps are embedded, do not gather their json or export_file since they - # are included in the current target, but do gather their deps'. + # If deps are embedded, do not gather their json or + # export_file since they are included in the current target, + # but do gather their deps'. deps_transitive_json_file.append(pkg_info.deps_transitive_json_file) deps_transitive_export_file.append(pkg_info.deps_transitive_export_file) deps_transitive_compiled_go_files.append(pkg_info.deps_transitive_compiled_go_files) - # Fetch the stdlib json from the first dependency - # if not stdlib_json_file: - # stdlib_json_file = pkg_info.stdlib_json_file - pkg_json_files = [] compiled_go_files = [] export_files = [] @@ -96,11 +89,11 @@ def _go_pkg_info_aspect_impl(target, ctx): pkg = _go_archive_to_pkg(archive) pkg_json_files.append(_make_pkg_json(ctx, archive, pkg)) - # if the rule is a test, we need to get the embedded go_library with the current - # test's sources. For that, consume the dependency via GoArchive.direct so that - # the test source files are there. Then, create the pkg json file directly. Only - # do that for direct dependencies that are not defined as deps, and use the - # importpath to find which. + # if the rule is a test, we need to get the embedded go_library with the + # current test's sources. For that, consume the dependency via + # GoArchive.direct so that the test source files are there. Then, create + # the pkg json file directly. Only do that for direct dependencies that + # are not defined as deps, and use the importpath to find which. if ctx.rule.kind == "go_test": deps_targets = [ dep[GoArchive].data.importpath @@ -114,13 +107,7 @@ def _go_pkg_info_aspect_impl(target, ctx): compiled_go_files.extend(archive.source.srcs) export_files.append(archive.data.export_file) - # If there was no stdlib json in any dependencies, fetch it from the - # current go_ node. - # if not stdlib_json_file: - # stdlib_json_file = ctx.attr._go_stdlib[GoStdLib]._list_json - pkg_info = GoPkgInfo( - # stdlib_json_file = stdlib_json_file, transitive_json_file = depset( direct = pkg_json_files, transitive = deps_transitive_json_file, @@ -150,17 +137,10 @@ def _go_pkg_info_aspect_impl(target, ctx): go_pkg_driver_json_file = pkg_info.transitive_json_file, go_pkg_driver_srcs = pkg_info.transitive_compiled_go_files, go_pkg_driver_export_file = pkg_info.transitive_export_file, - # go_pkg_driver_stdlib_json_file = depset([pkg_info.stdlib_json_file] if pkg_info.stdlib_json_file else []), - go_pkg_driver_stdlib_json_file = depset([]), ), ] go_pkg_info_aspect = aspect( implementation = _go_pkg_info_aspect_impl, attr_aspects = ["embed", "deps"], - attrs = { - "_go_stdlib": attr.label( - default = "@io_bazel_rules_go//:stdlib", - ), - }, ) From d943a0b4bb202f916889c9a9fbbdaf8e328d6071 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Mon, 13 Dec 2021 11:19:54 -0700 Subject: [PATCH 3/4] Make go_sdk configurable --- package.json | 13 +++++++++---- src/bezel/configuration.ts | 7 ++++++- src/bezel/download.ts | 2 +- src/golang/configuration.ts | 2 ++ src/golang/golang.ts | 12 +++++------- src/golang/settings.ts | 1 + 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 0bd30133..e2f34bcf 100644 --- a/package.json +++ b/package.json @@ -472,14 +472,19 @@ "description": "If false, disable the golang component", "default": true }, + "bsv.golang.gopackagesdriver.goSdkWorkspaceName": { + "type": "string", + "default": "go_sdk", + "description": "Name of the bazel external workspace for the go_sdk. This is used to calculate the GOROOT dir" + }, "bsv.golang.gopackagesdriver.release": { "type": "string", - "default": "v1.3.21", - "description": "The release tag of the gopackagesdriver executable" + "default": "v1.4.3", + "description": "The release tag of the gopackagesdriver client executable" }, "bsv.golang.gopackagesdriver.flags": { "type": "array", - "description": "optional flags for the gopackagesdriver executable", + "description": "optional flags for the gopackagesdriver client executable", "items": { "type": "string" }, @@ -857,4 +862,4 @@ "tabWidth": 2, "arrowParens": "avoid" } -} +} \ No newline at end of file diff --git a/src/bezel/configuration.ts b/src/bezel/configuration.ts index 79943bb5..d4181434 100644 --- a/src/bezel/configuration.ts +++ b/src/bezel/configuration.ts @@ -138,8 +138,12 @@ export interface LanguageServerConfiguration extends ComponentConfiguration { } export interface GopackagesdriverServerConfiguration { + // bind host for the gopackagesdriver server host: string; + // bind port for the gopackagesdriver server port: number; + // directory where server assets are located + workspaceDir: string; } @@ -376,6 +380,7 @@ export class LanguageServerSettings extends Settings { constructor( @@ -24,13 +24,12 @@ export class Golang extends RunnableComponent { const config = await this.settings.get(); const info = await this.bazel.getBazelInfo(); - const go = vscode.workspace.getConfiguration('go', this.workspaceFolder); - // ---- before --- - let goroot = go.get('goroot', '${output_base}/external/go_sdk'); - let toolsEnvVars = go.get<{ [key: string]: string }>('toolsEnvVars', {}) + const go = vscode.workspace.getConfiguration('go', this.workspaceFolder); + let toolsEnvVars = go.get<{ [key: string]: string }>('toolsEnvVars', {}) + let goroot = go.get('goroot', path.join('${output_base}', 'external', config.gopackagesdriver.goSdkWorkspaceName)); // go.get can actually return undefined? if (!goroot) { goroot = ''; @@ -40,7 +39,7 @@ export class Golang extends RunnableComponent { } const wizard = new GoplsWizard(new MultiStepInput(vscode.window)); - wizard.goroot = go.get('goroot', '${output_base}/external/go_sdk'); + wizard.goroot = goroot; // ---- run --- @@ -49,7 +48,6 @@ export class Golang extends RunnableComponent { // ---- after --- if (wizard.goroot) { - goroot = wizard.goroot.replace('${output_base}', info?.outputBase!); await go.update('goroot', goroot); } diff --git a/src/golang/settings.ts b/src/golang/settings.ts index 1b8089b9..9525c4f2 100644 --- a/src/golang/settings.ts +++ b/src/golang/settings.ts @@ -26,6 +26,7 @@ export class GolangSettings extends Settings { const cfg: GolangConfiguration = { enabled: config.get('enabled', true), gopackagesdriver: { + goSdkWorkspaceName: config.get('gopackagesdriver.goSdkWorkspaceName', 'go_sdk'), release: config.get('gopackagesdriver.release', 'v1.3.21'), executable: normalize(config.get('gopackagesdriver.executable', '')), flags: config.get('gopackagesdriver.flags'), From 4f23ba7f05ea50e2b2de3720381e24aa98cb8898 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Thu, 23 Dec 2021 12:48:50 -0700 Subject: [PATCH 4/4] Only log args if exists --- src/bezel/bes.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bezel/bes.ts b/src/bezel/bes.ts index 2a5b8ad5..2c0ad695 100644 --- a/src/bezel/bes.ts +++ b/src/bezel/bes.ts @@ -96,7 +96,9 @@ export class BuildEventService extends RunnableComponent { - console.log('bes write args', args); + if (args) { + console.log('bes write args', args); + } }); }); }