blob: 293060a2df8c1a1be9b305316ed1c1e25904d788 [file]
"""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 _original_import(canonical_path, js_extension):
return "./" + canonical_path.replace(".proto", js_extension)
def _replacement(package, name):
return ("./" + package if package else ".") + "/_virtual_imports/" + name
def _get_import_rewrites(deps, js_extension):
rewrites = []
for dep in deps:
proto_source_root = dep[ProtoInfo].proto_source_root
if not proto_source_root.endswith("/"):
proto_source_root = proto_source_root + "/"
if proto_source_root.find("/_virtual_imports/") != -1:
for source in dep[ProtoInfo].direct_sources:
canonical_path = source.path.removeprefix(proto_source_root)
rewrites.append(_original_import(canonical_path, js_extension) + ":" + _replacement(dep.label.package, dep.label.name))
return rewrites
def _js_proto_aspect_impl(target, ctx):
proto_info = target[ProtoInfo]
protoc_info = ctx.toolchains[PROTOC_TOOLCHAIN].proto
proto_lang_toolchain_info = ctx.toolchains[LANG_PROTO_TOOLCHAIN].proto
js_proto_toolchain_info = ctx.toolchains[LANG_PROTO_TOOLCHAIN].js
js_outputs = proto_common.declare_generated_files(ctx.actions, proto_info, js_proto_toolchain_info.out_js_extension)
dts_extension = js_proto_toolchain_info.out_dts_extension
dts_outputs = proto_common.declare_generated_files(ctx.actions, proto_info, dts_extension) if dts_extension else []
all_outputs = js_outputs + dts_outputs
# Tell the plugin how to fix up imports to account for any usage of
# import_prefix or strip_import_prefix on dependencies. For now we only do
# this with protoc-gen-es, but ideally we should generalize this logic to
# accommodate other plugins.
rewrite_args = None
if proto_lang_toolchain_info.plugin_format_flag.startswith("--plugin=protoc-gen-es="):
rewrite_args = [
"--es_opt=rewrite_imports=" + r
for r in _get_import_rewrites(ctx.rule.attr.deps, js_proto_toolchain_info.out_js_extension)
]
proto_common.compile(
actions = ctx.actions,
proto_lang_toolchain_info = proto_lang_toolchain_info,
protoc_info = protoc_info,
generated_files = all_outputs,
proto_info = proto_info,
mnemonic = "JsProtocGenerate",
progress_message = "Generating .js/.d.ts from %{label}",
host_path_separator = ctx.configuration.host_path_separator,
additional_args = rewrite_args,
)
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),
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,
],
)