Skip to content

go/tools: add gopackagesdriver#2858

Merged
achew22 merged 41 commits intomasterfrom
unknown repository
Jun 28, 2021
Merged

go/tools: add gopackagesdriver#2858
achew22 merged 41 commits intomasterfrom
unknown repository

Conversation

@steeve
Copy link
Copy Markdown
Contributor

@steeve steeve commented Apr 3, 2021

This commit introduces an aspect based GOPACKAGESDRIVER for rules_go.

It is rather clunky, but all VSCode + gopls features work with it:

  • Completion
  • Go to definition
  • Go to references
  • Quick lookup
  • Inline documentation

It does not support static usage at the moment (for instance by running it inside an action for codegen tools), but it could with few additions.

Usage

The packages driver fundamentally works from the perspective of one or more targets. In order
for it to function properly, the targets must be specified in the packages driver configuration,
with environment variables.

These are:

  • GOPACKAGESDRIVER_BAZEL_TARGETS: specifies specific targets, //... works too (although this is not recommended without tag filters). It is possible to set targets to something other than go_ rules, as long as the go_ nodes are reachable via the deps attribute in the graph. Such as when go_binary(linkmode = "c-archive") -> cc_library.
  • GOPACKAGESDRIVER_BAZEL_QUERY: runs a bazel query to select targets before, common one should be kind(go_binary, //...). This should work for most cases.
  • GOPACKAGESDRIVER_BAZEL_TAG_FILTERS: makes use of the --build_tag_filters option to only build (filter) targets with certain tags. This is useful for when you want each target to define wether or not it should be part of the packages list. For instance, set GOPACKAGESDRIVER_BAZEL_TARGETS="//..." and GOPACKAGESDRIVER_BAZEL_TAG_FILTERS="completion" to only build targets with the completion tag in the whole workspace.

In addition, those environment variables are optional:

  • GOPACKAGESDRIVER_BAZEL: bazel binary, defaults to bazel
  • BUILD_WORKSPACE_DIRECTORY: directory of the bazel workspace (should be auto detected when using a launcher script because it invokes bazel run)

1. gopls

You'll need gopls >= v0.6.10 (released on Apr 13th 2021). If you are using VSCode, it should be automatic.

2. Create a launcher script

Create a launcher script, say tools/gopackagesdriver.sh:

#!/usr/bin/env bash
exec bazel run -- @io_bazel_rules_go//go/tools/gopackagesdriver "${@}"

3. Sample VSCode configuration

In .vscode/settings.json add the following:

    "go.goroot": "${workspaceFolder}/bazel-${workspaceFolderBasename}/external/go_sdk",
    "go.toolsEnvVars": {
      "GOPACKAGESDRIVER_BAZEL_QUERY": "kind(go_binary, //...)",
      "GOPACKAGESDRIVER": "${workspaceFolder}/tools/gopackagesdriver.sh"
    },
    "go.useLanguageServer": true,

Open VSCode, and after a while the packages should be properly detected after the build is done.

The same principles should apply for vim-go or any editor that uses gopls.

Limitations

  • CGo completion may not work, but at least it's not explicitly supported.
  • Errors are not handled
  • Query patterns are ignored and the whole graph is returned each time patterns are now used
  • Root packages detection is probably wrong it should be better now

Technical details

Aspects

Most of the work is done by one aspect that generates a minimum package JSON entry, and extracts/forwards the Stdlib JSON packages file from the deepest go_ target in the graph (ie once all configurations/transitions are applied).

Each JSON file can contain one or more Go packages definition. Which are then loaded, their paths expanded to real absolute paths. Then, the package files are read to get the real package name and its imports list. Imports are then resolved to other packages in the graph.

stdlib packages

Because stdlib packages are not part of the bazel graph, the packages driver will open the .go files of each package and scan its imports to then resolve all packages, including of course stdlib.

The whole stdlib definition sits inside one JSON file, which is generated by the stdliblist builder. It executes go list builtin std runtime/cgo and then transforms its output to match the JSON packages.

This list file is fetched from the deepest go_ target in the graph so that all configurations and transitions are applied.

steeve added 15 commits April 4, 2021 01:36
This commit introduces the GOPACKAGESDRIVER for rules_go

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
Those files are generated and will end up in the temporary mod cache,
which isn't available later on.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
This handles some edges cases in which the import path last part is not
the package name.

Signed-off-by: Steeve Morin <steeve@zen.ly>
It's no use, and it can be a significant amount of files, so disable it.

Signed-off-by: Steeve Morin <steeve@zen.ly>
The indexing by ID was never used except for iterating on all packages.
The file index isn't used either since the whole graph is dumped anyway.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Needed when in a CGo environment for go list to work

Signed-off-by: Steeve Morin <steeve@zen.ly>
Fetch the stdlib JSON from the deepest target, and cascade it upward
so that transitions are applied properly. Also, this enables applying
the aspect to a target that depends on a go_binary, such as a cc_binary
with proper transition applied.

Signed-off-by: Steeve Morin <steeve@zen.ly>
This is more correct.

Signed-off-by: Steeve Morin <steeve@zen.ly>
This ensures that all possible packages are built

Signed-off-by: Steeve Morin <steeve@zen.ly>
The key types have to be copied and paster so might has well remove the
dependency altogether.

In the process, rework how the LoadMode is passed.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
Since the packages driver is meant to be run via `bazel run`, it's
simpler to leverage BUILD_WORKSPACE_DIRECTORY. It's one less parameter.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 5, 2021

Made some changes regarding UX, should be simpler now.

steeve added 3 commits April 5, 2021 12:19
Signed-off-by: Steeve Morin <steeve@zen.ly>
Will be easier when debugging.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Sometimes if the aspects explores nodes that won't have go_ rules,
there is no inner stdlib_json to fetch.

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
gopherbot pushed a commit to golang/tools that referenced this pull request Apr 6, 2021
Now that https://github.com/bazelbuild/rules_go has a working prototype of a `GOPACKAGESDRIVER`, it may be time to revert that commit.

The draft implementation is at bazel-contrib/rules_go#2858.

Change-Id: Ia738e8be448d936f8a3b2b421d0a765f94bbff52
GitHub-Last-Rev: 0df6c91
GitHub-Pull-Request: #297
Reviewed-on: https://go-review.googlesource.com/c/tools/+/307169
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 6, 2021

golang/tools#297 / https://go-review.googlesource.com/c/tools/+/307169 has been merged!
According to @stamblerre, it should be available with the v0.6.10 release!

@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 6, 2021

The check failure seems transient and unrelated to the PR:

Click to expand!
(18:25:14) ERROR: /workdir/BUILD.bazel:44:7: GoStdlib stdlib_/pkg failed: (Exit 34): com.google.devtools.build.lib.remote.BulkTransferException
--
  | at com.google.devtools.build.lib.remote.RemoteCache.waitForBulkTransfer(RemoteCache.java:227)
  | at com.google.devtools.build.lib.remote.RemoteCache.download(RemoteCache.java:338)
  | at com.google.devtools.build.lib.remote.RemoteSpawnRunner.downloadAndFinalizeSpawnResult(RemoteSpawnRunner.java:487)
  | at com.google.devtools.build.lib.remote.RemoteSpawnRunner.exec(RemoteSpawnRunner.java:307)
  | at com.google.devtools.build.lib.exec.SpawnRunner.execAsync(SpawnRunner.java:240)
  | at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:140)
  | at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:102)
  | at com.google.devtools.build.lib.actions.SpawnStrategy.beginExecution(SpawnStrategy.java:47)
  | at com.google.devtools.build.lib.exec.SpawnStrategyResolver.beginExecution(SpawnStrategyResolver.java:65)
  | at com.google.devtools.build.lib.analysis.actions.SpawnAction.beginExecution(SpawnAction.java:331)
  | at com.google.devtools.build.lib.actions.Action.execute(Action.java:127)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$5.execute(SkyframeActionExecutor.java:855)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.continueAction(SkyframeActionExecutor.java:1016)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.run(SkyframeActionExecutor.java:975)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionState.runStateMachine(ActionExecutionState.java:129)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionState.getResultOrDependOnFuture(ActionExecutionState.java:81)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.executeAction(SkyframeActionExecutor.java:472)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.checkCacheAndExecuteIfNeeded(ActionExecutionFunction.java:834)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.compute(ActionExecutionFunction.java:307)
  | at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:477)
  | at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:398)
  | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  | at java.base/java.lang.Thread.run(Unknown Source)
  | Suppressed: java.io.IOException: Output download failed: Expected digest '42dc2fda2ee5c8f91272d500517bbe67444e1c8ca6c1ec370c509d45a8781301/19453284' does not match received digest '6673104524e843567b1bc045c7ef0b70cba8da285a8f0a4c95eeaec1131897dc/19653284'.
  | at com.google.devtools.build.lib.remote.util.Utils.verifyBlobContents(Utils.java:201)
  | at com.google.devtools.build.lib.remote.GrpcCacheClient$1.onCompleted(GrpcCacheClient.java:372)
  | at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:476)
  | at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
  | at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
  | at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
  | at com.google.devtools.build.lib.remote.util.NetworkTime$NetworkTimeCall$1.onClose(NetworkTime.java:113)
  | at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:413)
  | at io.grpc.internal.ClientCallImpl.access$500(ClientCallImpl.java:66)
  | at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:742)
  | at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:721)
  | at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
  | at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
  | ... 3 more
  | . Note: Remote connection/protocol failed with: execution failed com.google.devtools.build.lib.remote.BulkTransferException
  | at com.google.devtools.build.lib.remote.RemoteCache.waitForBulkTransfer(RemoteCache.java:227)
  | at com.google.devtools.build.lib.remote.RemoteCache.download(RemoteCache.java:338)
  | at com.google.devtools.build.lib.remote.RemoteSpawnRunner.downloadAndFinalizeSpawnResult(RemoteSpawnRunner.java:487)
  | at com.google.devtools.build.lib.remote.RemoteSpawnRunner.exec(RemoteSpawnRunner.java:307)
  | at com.google.devtools.build.lib.exec.SpawnRunner.execAsync(SpawnRunner.java:240)
  | at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:140)
  | at com.google.devtools.build.lib.exec.AbstractSpawnStrategy.exec(AbstractSpawnStrategy.java:102)
  | at com.google.devtools.build.lib.actions.SpawnStrategy.beginExecution(SpawnStrategy.java:47)
  | at com.google.devtools.build.lib.exec.SpawnStrategyResolver.beginExecution(SpawnStrategyResolver.java:65)
  | at com.google.devtools.build.lib.analysis.actions.SpawnAction.beginExecution(SpawnAction.java:331)
  | at com.google.devtools.build.lib.actions.Action.execute(Action.java:127)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$5.execute(SkyframeActionExecutor.java:855)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.continueAction(SkyframeActionExecutor.java:1016)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor$ActionRunner.run(SkyframeActionExecutor.java:975)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionState.runStateMachine(ActionExecutionState.java:129)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionState.getResultOrDependOnFuture(ActionExecutionState.java:81)
  | at com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.executeAction(SkyframeActionExecutor.java:472)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.checkCacheAndExecuteIfNeeded(ActionExecutionFunction.java:834)
  | at com.google.devtools.build.lib.skyframe.ActionExecutionFunction.compute(ActionExecutionFunction.java:307)
  | at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:477)
  | at com.google.devtools.build.lib.concurrent.AbstractQueueVisitor$WrappedRunnable.run(AbstractQueueVisitor.java:398)
  | at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  | at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  | at java.base/java.lang.Thread.run(Unknown Source)
  | Suppressed: java.io.IOException: Output download failed: Expected digest '42dc2fda2ee5c8f91272d500517bbe67444e1c8ca6c1ec370c509d45a8781301/19453284' does not match received digest '6673104524e843567b1bc045c7ef0b70cba8da285a8f0a4c95eeaec1131897dc/19653284'.
  | at com.google.devtools.build.lib.remote.util.Utils.verifyBlobContents(Utils.java:201)
  | at com.google.devtools.build.lib.remote.GrpcCacheClient$1.onCompleted(GrpcCacheClient.java:372)
  | at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:476)
  | at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39)
  | at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23)
  | at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40)
  | at com.google.devtools.build.lib.remote.util.NetworkTime$NetworkTimeCall$1.onClose(NetworkTime.java:113)
  | at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:413)
  | at io.grpc.internal.ClientCallImpl.access$500(ClientCallImpl.java:66)
  | at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:742)
  | at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:721)
  | at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
  | at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
  | ... 3 more

steeve added 2 commits April 6, 2021 21:43
Use special flags in order to speed up the bazel query

Signed-off-by: Steeve Morin <steeve@zen.ly>
Signed-off-by: Steeve Morin <steeve@zen.ly>
steeve added 5 commits April 18, 2021 01:52
Signed-off-by: Steeve Morin <steeve@zen.ly>
simply print it and exit(1)

Signed-off-by: Steeve Morin <steeve@zen.ly>
…cher

Signed-off-by: Steeve Morin <steeve@zen.ly>
Since root packages are now properly computed, walk the package graph
from them.

This saves on JSON payload size a bit.

Signed-off-by: Steeve Morin <steeve@zen.ly>
@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 17, 2021

@jayconrod great review, I pushed most of the fixes

@pierreis
Copy link
Copy Markdown

My understanding is that this will fail if the project is library-only and doesn't contain any binary -- because of the bazel query. Any ideas?

@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 23, 2021

@pierreis it should work with a library too

@pierreis
Copy link
Copy Markdown

pierreis commented Apr 23, 2021

I am getting the following error -- which disappears as soon as I create a binary target:

Running: [bazel build --tool_tag=gopackagesdriver --ui_actions_shown=0 --show_result=0 --build_event_json_file=/tmp/gopackagesdriver_bep_057861429 --build_event_json_file_path_conversion=no --aspects=@io_bazel_rules_go//go/tools/gopackagesdriver:aspect.bzl%go_pkg_info_aspect --output_groups=go_pkg_driver_json_file,go_pkg_driver_stdlib_json_file,go_pkg_driver_export_file --keep_going ]
INFO: Invocation ID: 136ec814-ff31-45b4-48e7-d40d87af9de9
INFO: Streaming build results to: https://dev.remotebuid.k5.tk.internal/invocation/136ec814-ff31-45b4-48e7-d40d87af9de9
Loading: 
Loading: 0 packages loaded
ERROR: Skipping '': the empty string is not a valid target
WARNING: Target pattern parsing failed.
ERROR: Failed to parse target pattern even though it was previously parsed successfully: the empty string is not a valid target
INFO: Elapsed time: 0.656s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (0 packages loaded)
FAILED: Build did NOT complete successfully (0 packages loaded)
FAILED: Build did NOT complete successfully (0 packages loaded)
INFO: Streaming build results to: https://dev.remotebuid.k5.tk.internal/invocation/136ec814-ff31-45b4-48e7-d40d87af9de9
FAILED: Build did NOT complete successfully (0 packages loaded)
error: %!w(*fmt.wrapError=&{unable to build JSON files: unable to bazel build [--aspects=@io_bazel_rules_go//go/tools/gopackagesdriver:aspect.bzl%go_pkg_info_aspect --output_groups=go_pkg_driver_json_file,go_pkg_driver_stdlib_json_file,go_pkg_driver_export_file --keep_going ]: bazel build failed: exit status 1 0xc00012c0c0}): packages.Load errorgo list

@steeve
Copy link
Copy Markdown
Contributor Author

steeve commented Apr 23, 2021

It looks like your GOPACKAGESDRIVER_TARGETS or _QUERY isn't matching anything.

On the example I did make it match to go_binary, you should tweak it.

@pierreis
Copy link
Copy Markdown

Right, I did assume from the instructions that it had to be this query for some reason -- incorrectly it seems. Thanks!

@ribrdb
Copy link
Copy Markdown

ribrdb commented May 11, 2021

Is this waiting on anything else?

@jayconrod
Copy link
Copy Markdown
Collaborator

I think this is ready to merge, but we're still sorting out a repo access issue.

We'll probably do a minor release with this soon after.

@pierreis
Copy link
Copy Markdown

Is there any more work to do regarding error handling?

@ribrdb
Copy link
Copy Markdown

ribrdb commented May 12, 2021

Maybe it should return NotHandled: true when run outside a bazel workspace?

@pierreis
Copy link
Copy Markdown

My biggest pain point with the package driver at this moment is that, when using it, I loose all autocompletion or error highlighting as soon as the project fails to compile for any reason. Fixing the issue without error highlighting is much more painful than it should be.

@johanbrandhorst
Copy link
Copy Markdown
Contributor

What's the latest status of this PR? It'd be soooo nice to have this merged. Improvements could happen iteratively.

@achew22
Copy link
Copy Markdown
Member

achew22 commented Jun 28, 2021

@steeve thank you for the hard work on this one. I'm delighted to open the bug report floodgates.

@achew22 achew22 merged commit 0cd4433 into bazel-contrib:master Jun 28, 2021
@steeve steeve deleted the steeve/packagedriver branch June 28, 2021 19:20
@jayconrod
Copy link
Copy Markdown
Collaborator

Thank you @steeve! This is amazing.

I'll start preparing for a minor release this week or next week.

@yancl
Copy link
Copy Markdown

yancl commented Sep 23, 2021

Great!!!

When using gopackagedriver with vscode on macOS, I found that the gopls server uses too much CPU on and on. After some diagnose, I found the reason are that vscode will detect the workspace changes and send the workspace/didChangeWatchedFiles notifications to the gopls server, which will call gopackagedriver too.

The workspace changes are caused by the bazel build ... command, which will remove the symbol link from execroot and then re-create the symbol link in execroot on each build. So this loop will never stop(bazel build changes workspace, and changes in workspace tigger more bazel builds...)

Putting **/execroot/** to the files.watcherExclude configure of vscode will workaround this, hope that helps who meets the case:)

@raymondpebble
Copy link
Copy Markdown

@yancl I experienced the same thing and realized when copying the .vscode/settings.json from https://github.com/bazelbuild/rules_go/wiki/Editor-setup, I didn't update my package name:

"build.directoryFilters": [
      "-bazel-bin",
      "-bazel-out",
      "-bazel-testlogs",
      "-bazel-mypkg", <---here
    ],

That seemed to also fix it for me :)

@yancl
Copy link
Copy Markdown

yancl commented Oct 9, 2021

@raymondpebble Thanks for your information. The problem disappeared after I upgraded the version of the gopackagedriver from rules_go v0.28 to v0.29(https://github.com/bazelbuild/rules_go/releases/tag/v0.29.0) and remove the files.watcherExclude.

The .vscode/settings.json is copied from the rules_go vscode setting https://github.com/bazelbuild/rules_go/blob/master/.vscode/settings.json without build.directoryFilters configured. So I think maybe it is unrelated:)

yushan26 pushed a commit to yushan26/rules_go that referenced this pull request Jun 16, 2025
This makes non-Windows use the script bootstrap by default.

It's been a couple releases without any reported issues, so it seems
ready to become
the default.

Work towards bazel-contrib/rules_python#2156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants