diff --git a/MODULE.bazel b/MODULE.bazel index 9100e7b0f..1c4179389 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -8,6 +8,7 @@ module( # Lower-bounds (minimum) versions for direct runtime dependencies. # Do not bump these unless rules_js requires a newer version to function. +bazel_dep(name = "diff.bzl", version = "0.4.3") bazel_dep(name = "tar.bzl", version = "0.6.0") bazel_dep(name = "yq.bzl", version = "0.3.2") bazel_dep(name = "jq.bzl", version = "0.4.0") diff --git a/examples/proto/.bazelrc b/examples/proto/.bazelrc index 8484fe48a..6a2136b81 100644 --- a/examples/proto/.bazelrc +++ b/examples/proto/.bazelrc @@ -1,2 +1,3 @@ build --incompatible_enable_proto_toolchain_resolution build --@protobuf//bazel/toolchains:prefer_prebuilt_protoc +build --@diff.bzl//diff:validate diff --git a/examples/proto/BUILD.bazel b/examples/proto/BUILD.bazel index a60d6d72a..959a2b72e 100644 --- a/examples/proto/BUILD.bazel +++ b/examples/proto/BUILD.bazel @@ -1,4 +1,5 @@ -load("@aspect_rules_js//js:defs.bzl", "js_library", "js_test") +load("@aspect_rules_js//js:defs.bzl", "js_test") +load("@aspect_rules_js//js:proto.bzl", "js_proto_library") load("@npm//:defs.bzl", "npm_link_all_packages") load("@protobuf//bazel:proto_library.bzl", "proto_library") @@ -13,14 +14,18 @@ proto_library( deps = ["@protobuf//:timestamp_proto"], ) -js_library( - name = "foo_js_lib", - # This edge runs an aspect to produce a JsInfo provider with protobuf .js/.d.ts files. - deps = [":foo_proto"], +js_proto_library( + name = "foo_js_proto", + copy_types = ["{}_pb.d.ts".format(name.replace(".proto", "")) for name in glob(["*.proto"])], + # An aspect is attached to this edge to produce a JsInfo provider + proto = ":foo_proto", ) js_test( name = "test", - data = [":foo_js_lib"], + data = [ + ":foo_js_proto", + ":foo_js_proto.diff", + ], entry_point = "test_proto.js", ) diff --git a/examples/proto/status.proto b/examples/proto/status.proto index d461d7973..9c2ad5cdc 100644 --- a/examples/proto/status.proto +++ b/examples/proto/status.proto @@ -6,4 +6,5 @@ package status; message Status { google.protobuf.Timestamp created_at = 1; + string message = 2; } diff --git a/examples/proto/status_pb.d.ts b/examples/proto/status_pb.d.ts new file mode 100644 index 000000000..b129ee9e5 --- /dev/null +++ b/examples/proto/status_pb.d.ts @@ -0,0 +1,34 @@ +// @generated by protoc-gen-es v2.2.5 with parameter "keep_empty_files=true,target=js+dts,import_extension=js" +// @generated from file status.proto (package status, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; +import type { Message } from "@bufbuild/protobuf"; +import type { Timestamp } from "@bufbuild/protobuf/wkt"; + +/** + * Describes the file status.proto. + */ +export declare const file_status: GenFile; + +/** + * @generated from message status.Status + */ +export declare type Status = Message<"status.Status"> & { + /** + * @generated from field: google.protobuf.Timestamp created_at = 1; + */ + createdAt?: Timestamp; + + /** + * @generated from field: string message = 2; + */ + message: string; +}; + +/** + * Describes the message status.Status. + * Use `create(StatusSchema)` to create a new message. + */ +export declare const StatusSchema: GenMessage; + diff --git a/examples/proto/user_pb.d.ts b/examples/proto/user_pb.d.ts new file mode 100644 index 000000000..a194ef68f --- /dev/null +++ b/examples/proto/user_pb.d.ts @@ -0,0 +1,39 @@ +// @generated by protoc-gen-es v2.2.5 with parameter "keep_empty_files=true,target=js+dts,import_extension=js" +// @generated from file user.proto (syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; +import type { Message } from "@bufbuild/protobuf"; +import type { Status } from "./status_pb.js"; + +/** + * Describes the file user.proto. + */ +export declare const file_user: GenFile; + +/** + * @generated from message User + */ +export declare type User = Message<"User"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: status.Status status = 2; + */ + status?: Status; + + /** + * @generated from field: repeated string tags = 3; + */ + tags: string[]; +}; + +/** + * Describes the message User. + * Use `create(UserSchema)` to create a new message. + */ +export declare const UserSchema: GenMessage; + diff --git a/js/BUILD.bazel b/js/BUILD.bazel index 06e22df7b..6b99db226 100644 --- a/js/BUILD.bazel +++ b/js/BUILD.bazel @@ -28,6 +28,8 @@ bzl_library( visibility = ["//visibility:public"], deps = [ "//js/private:proto", + "@bazel_skylib//rules:select_file", + "@diff.bzl//diff:defs", "@protobuf//bazel/toolchains:proto_lang_toolchain_bzl", ], ) diff --git a/js/private/proto.bzl b/js/private/proto.bzl index 5f0e4d7f5..39b1d6b2e 100644 --- a/js/private/proto.bzl +++ b/js/private/proto.bzl @@ -90,3 +90,24 @@ js_proto_aspect = aspect( PROTOC_TOOLCHAIN, ], ) + +def _js_proto_library_impl(ctx): + return [ + OutputGroupInfo(types = ctx.attr.proto[JsInfo].types), + ctx.attr.proto[JsInfo], + ] + +js_proto_library = rule( + doc = """A rule that wraps a proto_library to invoke the js_proto_aspect. + + Useful when you must adapt a ProtoInfo provider to a JsInfo provider, for rule kinds that don't invoke the js_proto_aspect on their deps. + """, + implementation = _js_proto_library_impl, + attrs = { + "proto": attr.label( + mandatory = True, + providers = [ProtoInfo], + aspects = [js_proto_aspect], + ), + }, +) diff --git a/js/proto.bzl b/js/proto.bzl index 6eed3e20d..7c5304d2b 100644 --- a/js/proto.bzl +++ b/js/proto.bzl @@ -42,8 +42,10 @@ js_library( The generator you setup earlier will be invoked automatically as an action to generate the `.js` and `.d.ts` files. """ +load("@bazel_skylib//rules:select_file.bzl", "select_file") +load("@diff.bzl//diff:defs.bzl", "diff") load("@protobuf//bazel/toolchains:proto_lang_toolchain.bzl", "proto_lang_toolchain") -load("//js/private:proto.bzl", "LANG_PROTO_TOOLCHAIN") +load("//js/private:proto.bzl", "LANG_PROTO_TOOLCHAIN", js_proto_library_rule = "js_proto_library") def js_proto_toolchain(name, plugin_name, plugin_options, plugin_bin, runtime, **kwargs): """Define a proto_lang_toolchain that uses the plugin. @@ -97,3 +99,27 @@ def js_proto_toolchain(name, plugin_name, plugin_options, plugin_bin, runtime, * runtime = runtime, **kwargs ) + +def js_proto_library(name, proto, copy_types = []): + """Wrap a proto_library to invoke the js_proto_toolchain. + + Can copy .d.ts type definitions back to the source folder. + + Args: + name: The name of the library. + proto: The proto_library to wrap. + copy_types: HACK to get the label of the file in the source folder. + """ + + js_proto_library_rule(name = name, proto = proto) + if len(copy_types) > 0: + native.filegroup(name = "_{}.types".format(name), srcs = [name], output_group = "types") + for i, src_file in enumerate(copy_types): + gen_file = "_{}.gen_{}.d.ts".format(name, i) + select_file(name = gen_file, srcs = "_{}.types".format(name), subpath = src_file) + diff( + name = "{}.diff_{}".format(name, i), + file1 = src_file, + file2 = gen_file, + ) + native.filegroup(name = "{}.diff".format(name), srcs = ["{}.diff_{}".format(name, i) for i in range(len(copy_types))])