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

fix: Edge case where ts_proto_library depends on proto_library(strip_import_prefix=...) #759

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/proto_grpc/status_connect.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto

// @generated by protoc-gen-connect-es v1.4.0 with parameter "keep_empty_files=true,target=js+dts"
// @generated from file examples/proto_grpc/status.proto (package rpc, syntax proto3)
/* eslint-disable */
Expand Down
11 changes: 11 additions & 0 deletions examples/proto_grpc/status_pb.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto

// @generated by protoc-gen-es v1.8.0 with parameter "keep_empty_files=true,target=js+dts"
// @generated from file examples/proto_grpc/status.proto (package rpc, syntax proto3)
/* eslint-disable */
Expand All @@ -11,16 +13,25 @@ import { Message, proto3 } from "@bufbuild/protobuf";
*/
export declare class Status extends Message<Status> {
/**
* The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
*
* @generated from field: int32 code = 1;
*/
code: number;

/**
* A developer-facing error message, which should be in English. Any
* user-facing error message should be localized and sent in the
* [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
*
* @generated from field: string message = 2;
*/
message: string;

/**
* A list of messages that carry the error details. There will be a
* common set of message types for APIs to use.
*
* @generated from field: repeated google.protobuf.Any details = 3;
*/
details: Any[];
Expand Down
76 changes: 71 additions & 5 deletions ts/private/ts_proto_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,43 @@ load("@rules_proto//proto:proto_common.bzl", proto_toolchains = "toolchains")

_PROTO_TOOLCHAIN_TYPE = "@rules_proto//proto:toolchain_type"

# These 3 helpers are from "@protobuf//bazel/common:proto_common.bzl"
# After migrating, from @rules_proto to @protobuf, they can be replaced by a single load statement.
# See https://github.com/protocolbuffers/protobuf/blob/main/bazel/common/proto_common.bzl .

def _import_virtual_proto_path(path):
"""Imports all paths for virtual imports.

They're of the form:
'bazel-out/k8-fastbuild/bin/external/foo/e/_virtual_imports/e' or
'bazel-out/foo/k8-fastbuild/bin/e/_virtual_imports/e'"""
if path.count("/") > 4:
return "-I%s" % path
return None

def _import_repo_proto_path(path):
"""Imports all paths for generated files in external repositories.

They are of the form:
'bazel-out/k8-fastbuild/bin/external/foo' or
'bazel-out/foo/k8-fastbuild/bin'"""
path_count = path.count("/")
if path_count > 2 and path_count <= 4:
return "-I%s" % path
return None

def _import_main_output_proto_path(path):
"""Imports all paths for generated files or source files in external repositories.

They're of the form:
'bazel-out/k8-fastbuild/bin'
'external/foo'
'../foo'
"""
if path.count("/") <= 2 and path != ".":
return "-I%s" % path
return None

# buildifier: disable=function-docstring-header
def _protoc_action(ctx, proto_info, outputs):
"""Create an action like
Expand All @@ -18,6 +55,13 @@ def _protoc_action(ctx, proto_info, outputs):
"""
inputs = depset(proto_info.direct_sources, transitive = [proto_info.transitive_descriptor_sets])

# ensure that bin_dir doesn't get duplicated in the path
# e.g. by proto_library(strip_import_prefix=...)
proto_root = proto_info.proto_source_root
if proto_root.startswith(ctx.bin_dir.path):
proto_root = proto_root[len(ctx.bin_dir.path) + 1:]
plugin_output = ctx.bin_dir.path + "/" + proto_root

options = dict({
"keep_empty_files": True,
"target": "js+dts",
Expand All @@ -32,23 +76,38 @@ def _protoc_action(ctx, proto_info, outputs):
args.add_joined(["--plugin", "protoc-gen-es", ctx.executable.protoc_gen_es.path], join_with = "=")
for (key, value) in options.items():
args.add_joined(["--es_opt", key, value], join_with = "=")
args.add_joined(["--es_out", ctx.bin_dir.path], join_with = "=")
args.add_joined(["--es_out", plugin_output], join_with = "=")

if ctx.attr.gen_connect_es:
args.add_joined(["--plugin", "protoc-gen-connect-es", ctx.executable.protoc_gen_connect_es.path], join_with = "=")
for (key, value) in options.items():
args.add_joined(["--connect-es_opt", key, value], join_with = "=")
args.add_joined(["--connect-es_out", ctx.bin_dir.path], join_with = "=")
args.add_joined(["--connect-es_out", plugin_output], join_with = "=")

if ctx.attr.gen_connect_query:
args.add_joined(["--plugin", "protoc-gen-connect-query", ctx.executable.protoc_gen_connect_query.path], join_with = "=")
for (key, value) in options.items():
args.add_joined(["--connect-query_opt", key, value], join_with = "=")
args.add_joined(["--connect-query_out", ctx.bin_dir.path], join_with = "=")
args.add_joined(["--connect-query_out", plugin_output], join_with = "=")

args.add("--descriptor_set_in")
args.add_joined(proto_info.transitive_descriptor_sets, join_with = ctx.configuration.host_path_separator)

# Also from "@protobuf//bazel/common:proto_common.bzl":
#
# Protoc searches for .protos -I paths in order they are given and then
# uses the path within the directory as the package.
# This requires ordering the paths from most specific (longest) to least
# specific ones, so that no path in the list is a prefix of any of the
# following paths in the list.
# For example: 'bazel-out/k8-fastbuild/bin/external/foo' needs to be listed
# before 'bazel-out/k8-fastbuild/bin'. If not, protoc will discover file under
# the shorter path and use 'external/foo/...' as its package path.
args.add_all(proto_info.transitive_proto_path, map_each = _import_virtual_proto_path)
args.add_all(proto_info.transitive_proto_path, map_each = _import_repo_proto_path)
args.add_all(proto_info.transitive_proto_path, map_each = _import_main_output_proto_path)
args.add("-I.")

args.add_all(proto_info.direct_sources)

proto_toolchain_enabled = len(proto_toolchains.use_toolchain(_PROTO_TOOLCHAIN_TYPE)) > 0
Expand All @@ -72,10 +131,17 @@ def _declare_outs(ctx, info, ext):
if ctx.attr.gen_connect_es:
outs.extend(proto_common.declare_generated_files(ctx.actions, info, "_connect" + ext))
if ctx.attr.gen_connect_query:
proto_sources = info.direct_sources
proto_source_map = {src.basename: src for src in proto_sources}

# FIXME: we should refer to source files via labels instead of filenames
for proto, services in ctx.attr.gen_connect_query_service_mapping.items():
if not proto in proto_source_map:
fail("{} is not provided by proto_srcs".format(proto))
src = proto_source_map.get(proto)
prefix = proto.replace(".proto", "")
for service in services:
prefix = proto.replace(".proto", "")
outs.append(ctx.actions.declare_file("{}-{}_connectquery{}".format(prefix, service, ext)))
outs.append(ctx.actions.declare_file("{}-{}_connectquery{}".format(prefix, service, ext), sibling = src))

return outs

Expand Down
Loading