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

Add Prost/Tonic rules #479

Closed
wants to merge 1 commit into from
Closed

Add Prost/Tonic rules #479

wants to merge 1 commit into from

Conversation

dfreese
Copy link
Collaborator

@dfreese dfreese commented Nov 5, 2020

This is still very WIP in terms of its integration with rules_rust,
however, I wanted to put this up as a reference for those who may be
interested in pushing it forward more. Ideally, rules_rust would be
updated to expose an interface similar to rules_go:

https://github.com/bazelbuild/rules_go#protobuf-and-grpc

That allows plugins appropriate access into build internals so that the
generate code can be shifted by the user. This quite necessary for
rust, as there is no canonical rust api for protobuf/grpc; Prost/Tonic
is just one of them.

The current rust_proto_library and rust_grpc_library are tied to
grpc_rust and rust_protobuf crates currently, and this cannot be shifted
with the rust_protobuf_toolchain.

This PR contains a couple parts:

  1. Bazel rules and macros that define an external prost_library and
    tonic_library that can generate code from a proto_library target
  2. A prostgen executable that does the heavy lifting of running the
    code generation, and making sure that different prost_library
    targets can be used in different targets
  3. The cargo file with the requisite crates for prostgen
  4. A set of test cases that we had run into when using prost_library
    and tonic_library.

The //proto/prostgen/raze folder doesn't quite interoperate with the
//proto/raze folder, and I haven't had a chance to nail that down yet.
There are also some targets missing from WORKSPACE needed for the tests.

For googlers, the high-level approach is described in go/rust-protobuf.

This is still very WIP in terms of its integration with rules_rust,
however, I wanted to put this up as a reference for those who may be
interested in pushing it forward more.  Ideally, rules_rust would be
updated to expose an interface similar to rules_go:

https://github.com/bazelbuild/rules_go#protobuf-and-grpc

That allows plugins appropriate access into build internals so that the
generate code can be shifted by the user.  This quite necessary for
rust, as there is no canonical rust api for protobuf/grpc;  Prost/Tonic
is just one of them.

The current rust_proto_library and rust_grpc_library are tied to
grpc_rust and rust_protobuf crates currently, and this cannot be shifted
with the rust_protobuf_toolchain.

This PR contains a couple parts:

 1. Bazel rules and macros that define an external prost_library and
    tonic_library that can generate code from a proto_library target
 2. A prostgen executable that does the heavy lifting of running the
    code generation, and making sure that different prost_library
    targets can be used in different targets
 3. The cargo file with the requisite crates for prostgen
 4. A set of test cases that we had run into when using prost_library
    and tonic_library.

The //proto/prostgen/raze folder doesn't quite interoperate with the
//proto/raze folder, and I haven't had a chance to nail that down yet.
There are also some targets missing from WORKSPACE needed for the tests.

For googlers, the high-level approach is described in go/rust-protobuf.
@google-cla google-cla bot added the cla: yes label Nov 5, 2020
transitive = [transitive_sources, extern_descriptors],
),
env = {
# We've patched rustfmt to expect that it can resolve the path to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor nit: s/rustfmt/prost/ ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, yeah. That should be prost-build, not rustfmt.

let config = tonic_build::configure()
.build_client(self.grpc)
.build_server(self.grpc)
.format(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve only skimmed the build, but I didn’t see a data dep anywhere; is
this hermetic? (If so, I’d love to steal your incantation!)

cf. #388 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I see now that the _prost_generator rule is properly wired up to
the toolchain. I was hoping that there was a way that didn’t require
having to do that :-) but that should definitely work.

@GregBowyer
Copy link
Contributor

Something in here makes (at least my remote build setup) fail catastrophically

FAILED: Build did NOT complete successfully
Internal error thrown during build. Printing stack trace: java.lang.RuntimeException: Unrecoverable error while evaluating node 'ActionLookupData{actionLookupKey=ConfiguredTargetKey{label=//programl/proto:checkpoint_rs2_generated, config=BuildConfigurationValue.Key[9d75bcd9ca815b7411479ab8386be930e36ff1f9b4c151c61c96ea3c9b57b8c6]}, actionIndex=0}' (requested by nodes 'ActionLookupData{actionLookupKey=ConfiguredTargetKey{label=//programl/proto:checkpoint_rs2, config=BuildConfigurationValue.Key[9d75bcd9ca815b7411479ab8386be930e36ff1f9b4c151c61c96ea3c9b57b8c6]}, actionIndex=0}')
	at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:513)
	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)
Caused by: java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException: Protocol message end-group tag did not match expected tag.
	at com.google.devtools.build.lib.remote.RemoteCache.lambda$parseActionResultMetadata$6(RemoteCache.java:705)
	at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:242)
	at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:232)
	at com.google.common.util.concurrent.AbstractTransformFuture.run(AbstractTransformFuture.java:118)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.SettableFuture.set(SettableFuture.java:47)
	at com.google.devtools.build.lib.remote.RemoteCache$1.onSuccess(RemoteCache.java:268)
	at com.google.devtools.build.lib.remote.RemoteCache$1.onSuccess(RemoteCache.java:265)
	at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1089)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.AbstractCatchingFuture.run(AbstractCatchingFuture.java:110)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.AbstractCatchingFuture.run(AbstractCatchingFuture.java:110)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.SettableFuture.set(SettableFuture.java:47)
	at com.google.devtools.build.lib.remote.GrpcCacheClient$1.onCompleted(GrpcCacheClient.java:356)
	at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:447)
	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:93)
	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 io.grpc.internal.CensusStatsModule$StatsClientInterceptor$1$1.onClose(CensusStatsModule.java:700)
	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 io.grpc.internal.CensusTracingModule$TracingClientInterceptor$1$1.onClose(CensusTracingModule.java:399)
	at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:521)
	at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:66)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:641)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$700(ClientCallImpl.java:529)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:703)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:692)
	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
	at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
	... 3 more
Caused by: com.google.protobuf.InvalidProtocolBufferException: Protocol message end-group tag did not match expected tag.
	at com.google.protobuf.InvalidProtocolBufferException.invalidEndTag(InvalidProtocolBufferException.java:106)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.checkLastTagWas(CodedInputStream.java:635)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.readMessage(CodedInputStream.java:889)
	at build.bazel.remote.execution.v2.Directory.<init>(Directory.java:135)
	at build.bazel.remote.execution.v2.Directory.<init>(Directory.java:82)
	at build.bazel.remote.execution.v2.Directory$1.parsePartialFrom(Directory.java:2309)
	at build.bazel.remote.execution.v2.Directory$1.parsePartialFrom(Directory.java:2303)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.readMessage(CodedInputStream.java:888)
	at build.bazel.remote.execution.v2.Tree.<init>(Tree.java:64)
	at build.bazel.remote.execution.v2.Tree.<init>(Tree.java:15)
	at build.bazel.remote.execution.v2.Tree$1.parsePartialFrom(Tree.java:1160)
	at build.bazel.remote.execution.v2.Tree$1.parsePartialFrom(Tree.java:1154)
	at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:158)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:191)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:203)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:208)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:48)
	at build.bazel.remote.execution.v2.Tree.parseFrom(Tree.java:336)
	at com.google.devtools.build.lib.remote.RemoteCache.lambda$parseActionResultMetadata$6(RemoteCache.java:703)
	... 51 more
java.lang.RuntimeException: Unrecoverable error while evaluating node 'ActionLookupData{actionLookupKey=ConfiguredTargetKey{label=//programl/proto:checkpoint_rs2_generated, config=BuildConfigurationValue.Key[9d75bcd9ca815b7411479ab8386be930e36ff1f9b4c151c61c96ea3c9b57b8c6]}, actionIndex=0}' (requested by nodes 'ActionLookupData{actionLookupKey=ConfiguredTargetKey{label=//programl/proto:checkpoint_rs2, config=BuildConfigurationValue.Key[9d75bcd9ca815b7411479ab8386be930e36ff1f9b4c151c61c96ea3c9b57b8c6]}, actionIndex=0}')
	at com.google.devtools.build.skyframe.AbstractParallelEvaluator$Evaluate.run(AbstractParallelEvaluator.java:513)
	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)
Caused by: java.lang.RuntimeException: com.google.protobuf.InvalidProtocolBufferException: Protocol message end-group tag did not match expected tag.
	at com.google.devtools.build.lib.remote.RemoteCache.lambda$parseActionResultMetadata$6(RemoteCache.java:705)
	at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:242)
	at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:232)
	at com.google.common.util.concurrent.AbstractTransformFuture.run(AbstractTransformFuture.java:118)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.SettableFuture.set(SettableFuture.java:47)
	at com.google.devtools.build.lib.remote.RemoteCache$1.onSuccess(RemoteCache.java:268)
	at com.google.devtools.build.lib.remote.RemoteCache$1.onSuccess(RemoteCache.java:265)
	at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1089)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.AbstractCatchingFuture.run(AbstractCatchingFuture.java:110)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.AbstractCatchingFuture.run(AbstractCatchingFuture.java:110)
	at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
	at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:1174)
	at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:969)
	at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:738)
	at com.google.common.util.concurrent.SettableFuture.set(SettableFuture.java:47)
	at com.google.devtools.build.lib.remote.GrpcCacheClient$1.onCompleted(GrpcCacheClient.java:356)
	at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:447)
	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:93)
	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 io.grpc.internal.CensusStatsModule$StatsClientInterceptor$1$1.onClose(CensusStatsModule.java:700)
	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 io.grpc.internal.CensusTracingModule$TracingClientInterceptor$1$1.onClose(CensusTracingModule.java:399)
	at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:521)
	at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:66)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:641)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$700(ClientCallImpl.java:529)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:703)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:692)
	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
	at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)
	... 3 more
Caused by: com.google.protobuf.InvalidProtocolBufferException: Protocol message end-group tag did not match expected tag.
	at com.google.protobuf.InvalidProtocolBufferException.invalidEndTag(InvalidProtocolBufferException.java:106)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.checkLastTagWas(CodedInputStream.java:635)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.readMessage(CodedInputStream.java:889)
	at build.bazel.remote.execution.v2.Directory.<init>(Directory.java:135)
	at build.bazel.remote.execution.v2.Directory.<init>(Directory.java:82)
	at build.bazel.remote.execution.v2.Directory$1.parsePartialFrom(Directory.java:2309)
	at build.bazel.remote.execution.v2.Directory$1.parsePartialFrom(Directory.java:2303)
	at com.google.protobuf.CodedInputStream$ArrayDecoder.readMessage(CodedInputStream.java:888)
	at build.bazel.remote.execution.v2.Tree.<init>(Tree.java:64)
	at build.bazel.remote.execution.v2.Tree.<init>(Tree.java:15)
	at build.bazel.remote.execution.v2.Tree$1.parsePartialFrom(Tree.java:1160)
	at build.bazel.remote.execution.v2.Tree$1.parsePartialFrom(Tree.java:1154)
	at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:158)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:191)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:203)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:208)
	at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:48)
	at build.bazel.remote.execution.v2.Tree.parseFrom(Tree.java:336)
	at com.google.devtools.build.lib.remote.RemoteCache.lambda$parseActionResultMetadata$6(RemoteCache.java:703)
INFO: Streaming build results to: https://app.buildbuddy.io/invocation/7cdb08b8-8162-4837-9908-7f30556f7f93

),
"_generator": attr.label(
executable = True,
cfg = "host",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "exec"

"_protoc": attr.label(
doc = "The location of the `protoc` binary. It should be an executable target.",
executable = True,
cfg = "host",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "exec"

@dfreese
Copy link
Collaborator Author

dfreese commented Nov 16, 2020

Something in here makes (at least my remote build setup) fail catastrophically

I hadn't been super careful about host v. exec, so one thing that could cause a problem is that the prostgen binary should be marked for "exec" not "host".

The other thing that might be causing issues is the handling of the rustfmt and protoc binaries.

@GregBowyer
Copy link
Contributor

GregBowyer commented Nov 16, 2020 via email

@GregBowyer
Copy link
Contributor

GregBowyer commented Nov 16, 2020 via email

Comment on lines +98 to +113
# Since tonic_build has logic to run rustfmt, and does so by default
# for that matter, inject rustfmt into PATH, so that
# std::process::Command can find it. This seemed to be more clear
# than setting up a separate bazel run action, and removes another
# generated file. A run action can't have the same input and
# output file. This is also compatible with prostgen currently
# creating a lib.rs in the output directory, rather than taking in
# an output directory and an output file. The idea with only taking
# in an output directory was to try and leave open the possibility
# of using include! macros pointing towards the generated files.
# This might make things a little easier to read, rather than being
# extremely nested by package name.
#
# May not be perfect either, as the files are formatted before they
# get indented to match their module nesting.
"PATH": rustfmt.dirname,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

relevant section for tonic-build PR

Copy link
Collaborator

@UebelAndre UebelAndre Feb 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does hyperium/tonic#571 solve for this? What's the code change here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinging this question again, can you explain what would change should that PR get merged?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move to rustfmt as a library to solve this issue?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a change merged that allows you to specify RUSTFMT as a path to a rustfmt binary. So this could become: "RUSTFMT": rustfmt.path

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once 4.0.1 or higher comes out

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is out now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@tschuett tschuett Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any progress?

@UebelAndre
Copy link
Collaborator

I'm closing this out since no one is working on it and (despite it's age of the PR), the draft appears to be leading some folks to think the work is in progress. I'd be happy to see it reopened and pushed to completion since this would be an awesome feature!

@UebelAndre UebelAndre closed this Jan 18, 2023
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.

5 participants