Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go/tools: add gopackagesdriver #2858

Merged
merged 41 commits into from Jun 28, 2021
Merged

go/tools: add gopackagesdriver #2858

merged 41 commits into from Jun 28, 2021

Conversation

steeve
Copy link
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 <[email protected]>
Those files are generated and will end up in the temporary mod cache,
which isn't available later on.

Signed-off-by: Steeve Morin <[email protected]>
This handles some edges cases in which the import path last part is not
the package name.

Signed-off-by: Steeve Morin <[email protected]>
It's no use, and it can be a significant amount of files, so disable it.

Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
Needed when in a CGo environment for go list to work

Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
This ensures that all possible packages are built

Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
@steeve
Copy link
Contributor Author

steeve commented Apr 5, 2021

Made some changes regarding UX, should be simpler now.

Will be easier when debugging.

Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
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 bazelbuild/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 <[email protected]>
Reviewed-by: Heschi Kreinick <[email protected]>
Trust: Rebecca Stambler <[email protected]>
Run-TryBot: Rebecca Stambler <[email protected]>
gopls-CI: kokoro <[email protected]>
TryBot-Result: Go Bot <[email protected]>
@steeve
Copy link
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
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

Use special flags in order to speed up the bazel query

Signed-off-by: Steeve Morin <[email protected]>
simply print it and exit(1)

Signed-off-by: Steeve Morin <[email protected]>
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 <[email protected]>
@steeve
Copy link
Contributor Author

steeve commented Apr 17, 2021

@jayconrod great review, I pushed most of the fixes

@pierreis
Copy link

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
Contributor Author

steeve commented Apr 23, 2021

@pierreis it should work with a library too

@pierreis
Copy link

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
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

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

@ribrdb
Copy link

ribrdb commented May 11, 2021

Is this waiting on anything else?

@jayconrod
Copy link
Contributor

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

Is there any more work to do regarding error handling?

@ribrdb
Copy link

ribrdb commented May 12, 2021

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

@pierreis
Copy link

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
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
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 bazelbuild:master Jun 28, 2021
@steeve steeve deleted the steeve/packagedriver branch June 28, 2021 19:20
@jayconrod
Copy link
Contributor

Thank you @steeve! This is amazing.

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

@yancl
Copy link

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

@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

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:)

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