Skip to content
Merged
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
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ bazel_dep(name = "bazel_skylib", version = "1.5.0")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "rules_nodejs", version = "6.7.3")

# NB: LOWER BOUND on earliest BCR release of protobuf module, to avoid upgrading the root module by accident
bazel_dep(name = "protobuf", version = "3.19.6")

# Ensure any version of aspect_bazel_lib used includes:
# https://github.com/bazel-contrib/bazel-lib/commit/371362199e5e5bde58aeb15b54ea6cfb164af337
# https://github.com/bazel-contrib/bazel-lib/commit/ceaff54210291292f35d03bd4bbc0b6ceb40e77a
Expand Down
14 changes: 9 additions & 5 deletions js/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

exports_files(
glob(["*.bzl"]),
visibility = ["//visibility:public"],
)

bzl_library(
name = "defs",
srcs = ["defs.bzl"],
Expand All @@ -27,6 +22,15 @@ starlark_doc_extract(
deps = [":defs"],
)

bzl_library(
name = "proto",
srcs = ["proto.bzl"],
visibility = ["//visibility:public"],
deps = [
"//js/private:proto",
],
)

bzl_library(
name = "libs",
srcs = ["libs.bzl"],
Expand Down
18 changes: 16 additions & 2 deletions js/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

package(default_visibility = ["//visibility:public"])

exports_files(glob(["*.bzl"]))

exports_files([
"js_binary.sh.tpl",
"node_wrapper.bat",
Expand All @@ -24,6 +22,21 @@ bzl_library(
],
)

bzl_library(
name = "proto",
srcs = [
"proto.bzl",
"proto_common.bzl",
],
visibility = ["//visibility:public"],
deps = [
":js_helpers",
"//js:providers",
"@protobuf//bazel/common:proto_common_bzl",
"@protobuf//bazel/common:proto_info_bzl",
],
)

bzl_library(
name = "js_binary",
srcs = ["js_binary.bzl"],
Expand Down Expand Up @@ -56,6 +69,7 @@ bzl_library(
deps = [
":js_helpers",
":js_info",
":proto",
"@bazel_lib//lib:copy_to_bin",
"@bazel_skylib//lib:dicts",
"@bazel_tools//tools/build_defs/repo:cache.bzl",
Expand Down
2 changes: 2 additions & 0 deletions js/private/js_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ js_library(
load("@bazel_lib//lib:copy_to_bin.bzl", "COPY_FILE_TO_BIN_TOOLCHAINS")
load(":js_helpers.bzl", "copy_js_file_to_bin_action", "gather_runfiles")
load(":js_info.bzl", "JsInfo", "js_info")
load(":proto.bzl", "js_proto_aspect")

_DOC = """A library of JavaScript sources. Provides JsInfo, the primary provider used in rules_js
and derivative rule sets.
Expand Down Expand Up @@ -101,6 +102,7 @@ runtime dependency on this target.
{linked_npm_deps}
""".format(linked_npm_deps = _LINKED_NPM_DEPS_DOCSTRING),
providers = [JsInfo],
aspects = [js_proto_aspect],
),
"data": attr.label_list(
doc = """Runtime dependencies to include in binaries/tests that depend on this target.
Expand Down
92 changes: 92 additions & 0 deletions js/private/proto.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""An aspect that generates JavaScript and TypeScript code from .proto files.

This Bazel integration follows the "Local Generation" mechanism described at
https://connectrpc.com/docs/web/generating-code#local-generation,
using packages such as `@bufbuild/protoc-gen-es` and `@connectrpc/protoc-gen-connect-query`
as plugins to protoc.

The aspect converts a ProtoInfo provider into a JsInfo provider so that proto_library may be a dep to JS rules.
"""

load("@protobuf//bazel/common:proto_info.bzl", "ProtoInfo")
load("//js:providers.bzl", "JsInfo", "js_info")
load(
"//js/private:js_helpers.bzl",
"gather_npm_package_store_infos",
"gather_npm_sources",
"gather_transitive_sources",
"gather_transitive_types",
)
load(":proto_common.bzl", "proto_common")

LANG_PROTO_TOOLCHAIN = Label("//js/toolchains:protoc_plugin")
PROTOC_TOOLCHAIN = Label("@protobuf//bazel/private:proto_toolchain_type")

def _js_proto_aspect_impl(target, ctx):
Comment thread
alexeagle marked this conversation as resolved.
proto_info = target[ProtoInfo]
protoc_info = ctx.toolchains[PROTOC_TOOLCHAIN].proto
proto_lang_toolchain_info = ctx.toolchains[LANG_PROTO_TOOLCHAIN].proto
js_outputs = proto_common.declare_generated_files(ctx.actions, proto_info, "_pb.js")
dts_outputs = proto_common.declare_generated_files(ctx.actions, proto_info, "_pb.d.ts")
output_root = js_outputs[0].root

args = ctx.actions.args()
args.add(proto_lang_toolchain_info.plugin.executable, format = proto_lang_toolchain_info.plugin_format_flag)
proto_outdir = proto_common.output_directory(proto_info, output_root)
args.add_all((proto_lang_toolchain_info.out_replacement_format_flag % proto_outdir).split(" "))
args.add("--descriptor_set_in")
args.add_joined(proto_info.transitive_descriptor_sets, join_with = ctx.configuration.host_path_separator)

# Vendored: https://github.com/protocolbuffers/protobuf/blob/v31.1/bazel/common/proto_common.bzl#L193-L204
# 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 = proto_common.import_virtual_proto_path)
args.add_all(proto_info.transitive_proto_path, map_each = proto_common.import_repo_proto_path)
args.add_all(proto_info.transitive_proto_path, map_each = proto_common.import_main_output_proto_path)
args.add("-I.") # Needs to come last

args.add_all(proto_info.direct_sources)

ctx.actions.run(
executable = protoc_info.proto_compiler.executable,
arguments = [args],
progress_message = "Generating .js/.d.ts from %{label}",
mnemonic = "JsProtocGenerate",
env = {"BAZEL_BINDIR": output_root.path},
tools = [proto_lang_toolchain_info.plugin, protoc_info.proto_compiler],
inputs = depset(proto_info.direct_sources, transitive = [proto_info.transitive_descriptor_sets]),
outputs = js_outputs + dts_outputs,
use_default_shell_env = True,
)

return [
js_info(
target = ctx.label,
sources = depset(js_outputs),
types = depset(dts_outputs),
transitive_sources = gather_transitive_sources(js_outputs, ctx.rule.attr.deps),
Comment thread
alexeagle marked this conversation as resolved.
transitive_types = gather_transitive_types(dts_outputs, ctx.rule.attr.deps),
npm_sources = gather_npm_sources(srcs = [], deps = [proto_lang_toolchain_info.runtime]),
npm_package_store_infos = gather_npm_package_store_infos([proto_lang_toolchain_info.runtime]),
),
]

js_proto_aspect = aspect(
implementation = _js_proto_aspect_impl,
# Traverse the "deps" graph edges starting from the target
attr_aspects = ["deps"],
# Only visit nodes that produce a ProtoInfo provider
required_providers = [ProtoInfo],
# Be a valid dependency of a ts_project rule
provides = [JsInfo],
toolchains = [
LANG_PROTO_TOOLCHAIN,
PROTOC_TOOLCHAIN,
],
)
58 changes: 58 additions & 0 deletions js/private/proto_common.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Fork of
https://github.com/protocolbuffers/protobuf/blob/95dc8a02ad7d64527f0850c17edb1b86d3996ad8/bazel/common/proto_common.bzl#L15
that exposes some helper functions that _compile uses, so we can implement our own version of it.
"""

load("@protobuf//bazel/common:proto_common.bzl", _proto_common = "proto_common")

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

def _output_directory(proto_info, root):
proto_source_root = proto_info.proto_source_root
if proto_source_root.startswith(root.path):
#TODO: remove this branch when bin_dir is removed from proto_source_root
proto_source_root = proto_source_root.removeprefix(root.path).removeprefix("/")

if proto_source_root == "" or proto_source_root == ".":
return root.path

return root.path + "/" + proto_source_root

proto_common = struct(
Comment thread
alexeagle marked this conversation as resolved.
declare_generated_files = _proto_common.declare_generated_files,
import_virtual_proto_path = _import_virtual_proto_path,
import_repo_proto_path = _import_repo_proto_path,
import_main_output_proto_path = _import_main_output_proto_path,
output_directory = _output_directory,
)
16 changes: 16 additions & 0 deletions js/proto.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Protobuf and gRPC support for JavaScript and TypeScript.

See
- https://connectrpc.com/docs/web/getting-started
- https://connectrpc.com/docs/node/getting-started
"""

load("@protobuf//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain")
load("//js/private:proto.bzl", "LANG_PROTO_TOOLCHAIN")

def js_proto_toolchain(name, **kwargs):
proto_lang_toolchain(
name = name,
toolchain_type = LANG_PROTO_TOOLCHAIN,
**kwargs
)
3 changes: 3 additions & 0 deletions js/toolchains/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package(default_visibility = ["//visibility:public"])

toolchain_type(name = "protoc_plugin")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@dzbarsky should we drop this in contrib_toolchains ?

Loading