rules_proto_grpc-5.8.0/0000755000000000000000000000000000000000000013613 5ustar00rootrootrules_proto_grpc-5.8.0/BUILD.bazel0000644000000000000000000000030700000000000015471 0ustar00rootrootload("@bazel_skylib//:bzl_library.bzl", "bzl_library") bzl_library( name = "defs", srcs = ["defs.bzl"], visibility = ["//visibility:public"], deps = [ "//internal", ], ) rules_proto_grpc-5.8.0/MODULE.bazel0000644000000000000000000000062700000000000015624 0ustar00rootrootmodule( name = "rules_proto_grpc", version = "5.8.0", compatibility_level = 1, ) bazel_dep(name = "aspect_bazel_lib", version = "2.21.2") bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "protobuf", version = "31.1") bazel_dep(name = "rules_rust", version = "0.66.0") bazel_dep(name = "toolchains_protoc", version = "0.5.0") rules_proto_grpc-5.8.0/WORKSPACE0000644000000000000000000000000000000000000015062 0ustar00rootrootrules_proto_grpc-5.8.0/defs.bzl0000644000000000000000000000257400000000000015255 0ustar00rootroot"""Top level definition exports for rules_proto_grpc.""" load("//internal:common.bzl", _bazel_build_rule_common_attrs = "bazel_build_rule_common_attrs") load("//internal:compile.bzl", _proto_compile = "proto_compile", _proto_compile_attrs = "proto_compile_attrs", _proto_compile_impl = "proto_compile_impl") load("//internal:filter_files.bzl", _filter_files = "filter_files") load("//internal:plugin.bzl", _proto_plugin = "proto_plugin") load("//internal:providers.bzl", _ProtoCompileInfo = "ProtoCompileInfo", _ProtoPluginInfo = "ProtoPluginInfo") # Export providers ProtoPluginInfo = _ProtoPluginInfo ProtoCompileInfo = _ProtoCompileInfo # Export plugin rule proto_plugin = _proto_plugin # Export compile rule implementation, attrs and toolchains proto_compile_attrs = _proto_compile_attrs proto_compile_impl = _proto_compile_impl proto_compile_toolchains = [ # Convert to canonical Label strings str(Label("@protobuf//bazel/private:proto_toolchain_type")), str(Label("@aspect_bazel_lib//lib:coreutils_toolchain_type")), # For copy ] # Export compilation function, which can be wrapped by external rules that need more # pre-configuration than proto_compile_impl alone allows. e.g third party versions of # doc_template_compile_impl-like rules proto_compile = _proto_compile # Export utils bazel_build_rule_common_attrs = _bazel_build_rule_common_attrs filter_files = _filter_files rules_proto_grpc-5.8.0/internal/0000755000000000000000000000000000000000000015427 5ustar00rootrootrules_proto_grpc-5.8.0/internal/BUILD.bazel0000644000000000000000000000063500000000000017311 0ustar00rootrootload("@bazel_skylib//:bzl_library.bzl", "bzl_library") bzl_library( name = "internal", srcs = [ "common.bzl", "compile.bzl", "filter_files.bzl", "plugin.bzl", "protoc.bzl", "providers.bzl", ], visibility = ["//:__subpackages__"], deps = [ "@protobuf//bazel:proto_library_bzl", "@protobuf//bazel/common:proto_info_bzl", ], ) rules_proto_grpc-5.8.0/internal/common.bzl0000644000000000000000000003061400000000000017434 0ustar00rootroot"""Common generic rules used by rules_proto_grpc.""" _objc_upper_segments = { "url": "URL", "http": "HTTP", "https": "HTTPS", } def capitalize(s): """ Capitalize a string - only first letter Args: s (string): The input string to be capitalized. Returns: (string): The capitalized string. """ return s[0:1].upper() + s[1:] def pascal_objc(s): """ Convert pascal_case -> PascalCase Objective C uses pascal case, but there are exceptions that it uppercases. For example, it will uppercase the words "url", "http", and "https". https://github.com/protocolbuffers/protobuf/blob/54176b26a9be6c9903b375596b778f51f5947921/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc#L91 Args: s (string): The input string to be capitalized. Returns: (string): The capitalized string. """ # It is possible for proto files to be named with dashes. # e.g. company-proto.proto, this ensures the output file names for ObjC # correctly match. s = s.replace("-", "_") segments = [] current = "" last_char_was_number = False last_char_was_lower = False last_char_was_upper = False for char in s.elems(): if char.isdigit(): if last_char_was_number: segments.append(current) current = "" current += char last_char_was_number = True last_char_was_lower = False last_char_was_upper = False elif char.islower(): if not last_char_was_lower and not last_char_was_upper: segments.append(current) current = "" current += char last_char_was_number = False last_char_was_lower = True last_char_was_upper = False elif char.isupper(): if not last_char_was_upper: segments.append(current) current = "" current += char.lower() last_char_was_number = False last_char_was_lower = False last_char_was_upper = True else: last_char_was_number = False last_char_was_lower = False last_char_was_upper = False segments.append(current) new_segments = [] for segment in segments: repl = _objc_upper_segments.get(segment) if repl: segment = repl else: segment = capitalize(segment) new_segments.append(segment) return "".join(new_segments) def pascal_case(s): """ Convert pascal_case -> PascalCase Args: s (string): The input string to be capitalized. Returns: (string): The capitalized string. """ return "".join([capitalize(part) for part in s.split("_")]) def python_path(s): """ Convert a path string to a python import compatible path as is generated by the python plugin. Python import paths cannot contain dashes, so these are replaced by underscores. See https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/compiler/python/python_generator.cc#L89-L95 The dots in the file path are replaced by slashes: https://github.com/protocolbuffers/protobuf/blob/v21.7/src/google/protobuf/compiler/python/helpers.cc#L95 Args: s (string): The input string to be converted. Returns: (string): The converted string. """ return s.replace("-", "_").replace(".", "/") def php_path(s): """ Convert a path string to a php path as is generated by the php plugin. Args: s (string): The input string to be converted. Returns: (string): The converted string. """ return "/".join([capitalize(c) for c in s.split("/")]) def snake_case(s): """ Convert a path string from camelCase to snake_case. Args: s (string): The input string to be converted. Returns: (string): The converted string. """ converted = "" is_last_lower_case = False for char in s.elems(): if char.isupper(): if is_last_lower_case: converted += "_" + char is_last_lower_case = False continue elif char.islower() or char.isdigit(): is_last_lower_case = True converted += char continue is_last_lower_case = False converted += char return converted.lower() def dasherize(s): """ Convert a path string from snake_case to dashed-case. Args: s (string): The input string to be converted. Returns: (string): The converted string. """ return snake_case(s).replace("_", "-") def get_output_filename(src_file, pattern, proto_info): """ Build the predicted filename for file generated by the given plugin. A 'proto_plugin' rule allows one to define the predicted outputs. For flexibility, we allow special tokens in the output filename that get replaced here. The overall pattern is '{token}' mimicking the python 'format' feature. Additionally, there are '|' characters like '{basename|pascal}' that can be read as 'take the basename and pipe that through the pascal function'. Args: src_file: the .proto pattern: the input pattern string proto_info: The object Returns: The replaced string """ # Get proto path and strip extension protopath = descriptor_proto_path(src_file, proto_info) if protopath.endswith(".proto"): protopath = protopath[:-6] elif protopath.endswith(".protodevel"): protopath = protopath[:-11] protopath_partitions = protopath.rpartition("/") # Get output basename and strip extension basename = src_file.basename if basename.endswith(".proto"): basename = basename[:-6] elif basename.endswith(".protodevel"): basename = basename[:-11] # Replace tokens filename = "" if "{basename}" in pattern: filename = pattern.replace("{basename}", basename) elif "{protopath}" in pattern: filename = pattern.replace("{protopath}", protopath) elif "{basename|pascal}" in pattern: filename = pattern.replace("{basename|pascal}", pascal_case(basename)) elif "{protopath|pascal}" in pattern: filename = pattern.replace("{protopath|pascal}", "/".join([ # Pascal case only the file name protopath_partitions[0], pascal_case(protopath_partitions[2]), ])) elif "{basename|python}" in pattern: filename = pattern.replace("{basename|python}", python_path(basename)) elif "{protopath|python}" in pattern: filename = pattern.replace("{protopath|python}", python_path(protopath)) elif "{basename|php}" in pattern: filename = pattern.replace("{basename|php}", php_path(basename)) elif "{protopath|php}" in pattern: filename = pattern.replace("{protopath|php}", php_path(protopath)) elif "{basename|pascal|objc}" in pattern: filename = pattern.replace("{basename|pascal|objc}", pascal_objc(basename)) elif "{protopath|pascal|objc}" in pattern: filename = pattern.replace("{protopath|pascal|objc}", "/".join([ # Pascal case only the file name protopath_partitions[0], pascal_objc(protopath_partitions[2]), ])) elif "{protopath|dasherize}" in pattern: filename = pattern.replace("{protopath|dasherize}", "/".join([ # Dasherize only the file name protopath_partitions[0], dasherize(protopath_partitions[2]), ])) else: filename += basename + pattern return filename def copy_file(ctx, src_file, dest_path, sibling = None): """ Copy a file to a new path destination Args: ctx: the object src_file: the source file dest_path: the destination path of the file sibling: a file to use as a sibling to declare_file Returns: for the copied file """ coreutils_toolchain_label = Label("@aspect_bazel_lib//lib:coreutils_toolchain_type") coreutils = ctx.toolchains[coreutils_toolchain_label].coreutils_info dest_file = ctx.actions.declare_file(dest_path, sibling = sibling) ctx.actions.run( executable = coreutils.bin, arguments = ["cp", src_file.path, dest_file.path], inputs = [src_file], outputs = [dest_file], mnemonic = "CopyFile", progress_message = "copying file {} to {}".format(src_file.path, dest_file.path), toolchain = coreutils_toolchain_label, ) return dest_file def descriptor_proto_path(proto, proto_info): """ Convert a proto File to the path within the descriptor file. Adapted from https://github.com/bazelbuild/rules_go Args: proto: The proto file. proto_info: The ProtoInfo provider the file is from. Returns: The path to the proto file, as seen when making the descriptor. """ # Strip proto_source_root path = strip_path_prefix(proto.path, proto_info.proto_source_root) # Strip root path = strip_path_prefix(path, proto.root.path) # Strip workspace root path = strip_path_prefix(path, proto.owner.workspace_root) return path def strip_path_prefix(path, prefix): """ Strip a prefix from a path if it exists and any remaining prefix slashes. Args: path: The path string to strip. prefix: The prefix to remove. Returns: The stripped path. If the prefix is not present, this will be the same as the input. """ if path.startswith(prefix): path = path[len(prefix):] if path.startswith("/"): path = path[1:] return path def parse_version(version_str): """ Parse the bazel version string, returning a tuple. Derived from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/version_check.bzl Args: version_str: The version string to parse. Returns: The tuple of version elements. """ # Extract just the semver part semver_str = version_str.partition(" ")[0].partition("-")[0] # Return version tuple return tuple([n for n in semver_str.split(".")]) # Check that a specific bazel version is being used. def check_bazel_minimum_version(minimum_bazel_version): """ Check that the bazel version meets or exceeds the required version. Derived from https://github.com/tensorflow/tensorflow/blob/master/tensorflow/version_check.bzl Args: minimum_bazel_version: The minimum supported version Returns: Nothing """ # Check for bazel versions that did not support version checking if "bazel_version" not in dir(native): fail("Bazel version is lower than 0.2.1, rules_proto_grpc requires at least {}".format(minimum_bazel_version)) # Check for non-release versions if not native.bazel_version: print("Bazel is not a release version, rules_proto_grpc requires at least {}".format(minimum_bazel_version)) # buildifier: disable=print return # Check version strings current_version_tuple = parse_version(native.bazel_version) minimum_version_tuple = parse_version(minimum_bazel_version) if current_version_tuple < minimum_version_tuple: fail( "Bazel version is {}, rules_proto_grpc requires at least {}".format( native.bazel_version, minimum_bazel_version, ), ) def get_package_root(ctx): """ Get the full path to the package output directory, relative to workspace root. Args: ctx: The Bazel rule execution context object. Returns: The full path. """ package_root = ctx.bin_dir.path if ctx.label.workspace_root: package_root += "/" + ctx.label.workspace_root if ctx.label.package: package_root += "/" + ctx.label.package return package_root # All common build attrs from https://bazel.build/reference/be/common-definitions#common-attributes # These are listed here to be correctly propagated through *_library macros to the underlying # rules without just passing the entirety of kwargs, although maybe that's the better choice... bazel_build_rule_common_attrs = [ "compatible_with", "deprecation", "distribs", "exec_compatible_with", "exec_properties", "features", "restricted_to", "tags", "target_compatible_with", "testonly", "toolchains", "visibility", ] rules_proto_grpc-5.8.0/internal/compile.bzl0000644000000000000000000005125700000000000017602 0ustar00rootroot"""Compilation rules definition for rules_proto_grpc.""" load("@protobuf//bazel/common:proto_info.bzl", "ProtoInfo") load( "//internal:common.bzl", "copy_file", "descriptor_proto_path", "get_output_filename", "get_package_root", "strip_path_prefix", ) load("//internal:protoc.bzl", "build_protoc_args") load("//internal:providers.bzl", "ProtoCompileInfo", "ProtoPluginInfo") proto_compile_attrs = { "protos": attr.label_list( mandatory = True, providers = [ProtoInfo], doc = "List of labels that provide the ProtoInfo provider (such as proto_library from @protobuf)", ), "options": attr.string_list_dict( doc = "Extra options to pass to plugins, as a dict of plugin label -> list of strings. The key * can be used exclusively to apply to all plugins", ), "verbose": attr.int( doc = "The verbosity level. Supported values and results are 0: Show nothing, 1: Show command, 2: Show command and sandbox after running protoc, 3: Show command and sandbox before and after running protoc, 4. Show env, command, expected outputs and sandbox before and after running protoc", ), "prefix_path": attr.string( doc = "Path to prefix to the generated files in the output directory", ), "extra_protoc_args": attr.string_list( doc = "A list of extra args to pass directly to protoc, not as plugin options", ), "extra_protoc_files": attr.label_list( allow_files = True, doc = "List of labels that provide extra files to be available during protoc execution", ), "extra_plugins": attr.label_list( cfg = "exec", doc = "List of extra protoc plugins to use during compilation", ), "output_mode": attr.string( default = "PREFIXED", values = ["PREFIXED", "NO_PREFIX", "NO_PREFIX_FLAT"], doc = "The output mode for the target. PREFIXED (the default) will output to a directory named by the target within the current package root, NO_PREFIX will output files directly to the current package, NO_PREFIX_FLAT will ouput directly to the current package without mirroring the package tree. Using NO_PREFIX may lead to conflicting writes", ), "_fixer": attr.label( default = "@rules_proto_grpc//tools/fixer", cfg = "exec", allow_files = True, executable = True, doc = "Label of the fixer target", ), "_merger": attr.label( default = "@rules_proto_grpc//tools/merger", cfg = "exec", allow_files = True, executable = True, doc = "Label of the merger target", ), } def proto_compile_impl(ctx): """ Common implementation function for lang_*_compile rules. Args: ctx: The Bazel rule execution context object. Returns: Providers: - ProtoCompileInfo - DefaultInfo """ # Load attrs that we pass as args # This is done to allow writing rules that can call proto_compile with mutable attributes, # such as in doc_template_compile options = ctx.attr.options extra_protoc_args = getattr(ctx.attr, "extra_protoc_args", []) extra_protoc_files = ctx.files.extra_protoc_files # Execute with extracted attrs return proto_compile(ctx, options, extra_protoc_args, extra_protoc_files) def proto_compile(ctx, options, extra_protoc_args, extra_protoc_files): """ Common implementation function for lang_*_compile rules. Args: ctx: The Bazel rule execution context object. options: The mutable options dict. extra_protoc_args: The mutable extra_protoc_args list. extra_protoc_files: The mutable extra_protoc_files list. Returns: Providers: - ProtoCompileInfo - DefaultInfo """ # Load attrs proto_infos = [dep[ProtoInfo] for dep in ctx.attr.protos] plugins = [ plugin[ProtoPluginInfo] for plugin in ctx.attr._plugins ] + [ plugin[ProtoPluginInfo] for plugin in ctx.attr.extra_plugins ] verbose = ctx.attr.verbose # Load toolchains and tools protoc_toolchain_label = Label("@protobuf//bazel/private:proto_toolchain_type") protoc_toolchain_info = ctx.toolchains[protoc_toolchain_label] protoc = protoc_toolchain_info.proto.proto_compiler.executable fixer = ctx.executable._fixer merger = ctx.executable._merger # The directory where the outputs will be generated, relative to the package. # A temporary dir is used here to allow output directories that may need to be merged later rel_premerge_root = "_rpg_premerge_" + ctx.label.name # The full path to the pre-merge output root, relative to the workspace premerge_root = get_package_root(ctx) + "/" + rel_premerge_root # The lists of generated files and directories that we expect to be produced, in their pre-merge # locations premerge_files = [] premerge_dirs = [] # Convert options dict to label keys plugin_labels = [plugin.label for plugin in plugins] per_plugin_options = {} for plugin_label_str, opts in options.items(): if plugin_label_str == "*": continue plugin_label = Label(plugin_label_str) if plugin_label not in plugin_labels: # Attempt to match a canonical label with the label string provided. This is very much a # hack that is likely to break, but there is no attr.label_list_dict and we need to # accept "*" anyway. This turns a plugin label like # @@rules_proto_grpc_grpc_gateway~override//:plugin into # @rules_proto_grpc_grpc_gateway//:plugin # Note that pre Bazel 8 a ~ is used and post Bazel 8 a + is used, see # https://github.com/bazelbuild/bazel/issues/23127 for possible_plugin_label in plugin_labels: if ( (str(possible_plugin_label).partition("//"))[0].partition("~")[0].partition("+")[0].replace("@@", "@") + "//" + str(possible_plugin_label).partition("//")[2] ) == plugin_label_str: plugin_label = possible_plugin_label if plugin_label not in plugin_labels: fail("The options attr on target {} contains a plugin label {} for a plugin that does not exist on this rule. The available plugins are {} ".format(ctx.label, plugin_label, plugin_labels)) per_plugin_options[Label(plugin_label)] = [ opt.replace("{name}", ctx.label.name) for opt in opts ] # Only allow '*' by itself all_plugin_options = [] # Options applied to all plugins, from the '*' key if "*" in options: if len(options) > 1: fail("The options attr on target {} cannot contain '*' and other labels. Use either '*' or labels".format(ctx.label)) all_plugin_options = [ opt.replace("{name}", ctx.label.name) for opt in options["*"] ] ### ### Setup plugins ### # Each plugin is isolated to its own execution of protoc, as plugins may have differing # exclusions that cannot be expressed in a single protoc execution for all plugins. for plugin in plugins: # Create unique plugin ID for paths, as multiple plugins may share the same name # e.g. grpc_plugin plugin_id = "{}_{}_{}".format( plugin.label.repo_name, plugin.label.package.replace("/", "_"), plugin.label.name.replace("/", "_"), ) ### ### Check plugin ### # Check plugin outputs if plugin.output_directory and (plugin.out or plugin.outputs or plugin.empty_template): fail("Proto plugin {} cannot use output_directory in conjunction with outputs, out or empty_template".format(plugin.name)) ### ### Gather proto files and filter by exclusions ### protos = [] # The filtered set of .proto files to compile plugin_outputs = [] proto_paths = [] # The paths passed to protoc for proto_info in proto_infos: for proto in proto_info.direct_sources: # Check for exclusion if any([ proto.dirname.endswith(exclusion) or proto.path.endswith(exclusion) for exclusion in plugin.exclusions ]) or proto in protos: # When using import_prefix, the ProtoInfo.direct_sources list appears to contain # duplicate records, the final check 'proto in protos' removes these. See # https://github.com/bazelbuild/bazel/issues/9127 continue # Proto not excluded protos.append(proto) # Add per-proto outputs for pattern in plugin.outputs: plugin_outputs.append(ctx.actions.declare_file("{}/{}".format( rel_premerge_root, get_output_filename(proto, pattern, proto_info), ))) # Get proto path for protoc proto_paths.append(descriptor_proto_path(proto, proto_info)) # Skip plugin if all proto files have now been excluded if len(protos) == 0: if verbose > 2: print( 'Skipping plugin "{}" for "{}" as all proto files have been excluded'.format( plugin.name, ctx.label, ), ) # buildifier: disable=print continue # Append current plugin outputs to global outputs before looking at per-plugin outputs; # these are manually added globally as there may be srcjar outputs. premerge_files.extend(plugin_outputs) ### ### Declare per-plugin outputs ### # Some protoc plugins generate a set of output files (like python) while others generate a # single 'archive' file that contains the individual outputs (like java). Jar outputs are # gathered as a special case as we need to post-process them to have a 'srcjar' extension # (java_library rules don't accept source jars with a 'jar' extension). out_file = None if plugin.out: # Define out file out_file = ctx.actions.declare_file("{}/{}".format( rel_premerge_root, plugin.out.replace("{name}", ctx.label.name), )) plugin_outputs.append(out_file) if not out_file.path.endswith(".jar"): # Add output direct to global outputs premerge_files.append(out_file) else: # Create .srcjar from .jar for global outputs premerge_files.append(copy_file( ctx, out_file, "{}.srcjar".format(out_file.basename.rpartition(".")[0]), sibling = out_file, )) ### ### Declare plugin output directory if required ### # Some plugins outputs a structure that cannot be predicted from the input file paths alone. # For these plugins, we simply declare the directory. if plugin.output_directory: out_file = ctx.actions.declare_directory(rel_premerge_root + "/" + "_plugin_" + plugin_id) plugin_outputs.append(out_file) premerge_dirs.append(out_file) ### ### Build command ### # Determine the outputs expected by protoc. # When plugin.empty_template is not set, protoc will output directly to the final targets. # When set, we will direct the plugin outputs to a temporary folder, then use the fixer # executable to write to the final targets. if plugin.empty_template: # Create paths manifest for fixer fixer_manifest_file = ctx.actions.declare_file(rel_premerge_root + "/" + "_plugin_fixer_manifest_" + plugin_id + ".txt") ctx.actions.write(fixer_manifest_file, "\n".join([ strip_path_prefix(file.path, premerge_root) # Path of the file relative to the output root for file in plugin_outputs ])) # Create output directory for protoc to write into fixer_dir = ctx.actions.declare_directory( rel_premerge_root + "/" + "_plugin_unfixed_" + plugin_id, ) out_arg = fixer_dir.path plugin_protoc_outputs = [fixer_dir] # Apply fixer ctx.actions.run( inputs = [fixer_manifest_file, fixer_dir, plugin.empty_template], outputs = plugin_outputs, arguments = [ fixer_manifest_file.path, plugin.empty_template.path, fixer_dir.path, premerge_root, ], progress_message = "Applying fixer for {} plugin on target {}".format( plugin.name, ctx.label, ), executable = fixer, ) else: # No fixer, protoc writes files directly if out_file and "QUIRK_OUT_PASS_ROOT" not in plugin.quirks: # Single output file, pass the full file name to out arg, unless QUIRK_OUT_PASS_ROOT # quirk is in use out_arg = out_file.path else: # No single output (or QUIRK_OUT_PASS_ROOT enabled), pass root dir out_arg = premerge_root plugin_protoc_outputs = plugin_outputs # Build argument list for protoc execution args_list, cmd_inputs = build_protoc_args( ctx, plugin, proto_infos, out_arg, extra_options = all_plugin_options + per_plugin_options.get(plugin.label, []), extra_protoc_args = extra_protoc_args, ) args = ctx.actions.args() args.set_param_file_format("multiline") args.use_param_file("@%s", use_always = False) args.add_all(args_list) # Add import roots and files if required by plugin # By default we pass just the descriptors and the proto paths, but these may not contain # all of the comments etc from the source files if "QUIRK_DIRECT_MODE" in plugin.quirks: args.add_all([ "--proto_path=" + proto_info.proto_source_root for proto_info in proto_infos ]) cmd_inputs += protos # Add source proto files as descriptor paths for proto_path in proto_paths: args.add(proto_path) ### ### Specify protoc action ### mnemonic = "ProtoCompile" cmd_inputs += extra_protoc_files # Get tool executable via tool provider, rather than via ctx.executable # See https://github.com/bazelbuild/bazel/issues/22249 tools = ([plugin.tool_provider.files_to_run] if plugin.tool_provider else []) # Print debug info if verbose > 0: print("{}:".format(mnemonic), protoc.path, args) # buildifier: disable=print # Check env attr exclusivity if plugin.env and plugin.use_built_in_shell_environment: fail( "Plugin env and use_built_in_shell_environment attributes are mutually exclusive; " + " both set for plugin {}".format(plugin.name), ) # Build protoc env for plugin, with replacement # See https://github.com/rules-proto-grpc/rules_proto_grpc/pull/226 plugin_env = { k: v.replace("{bindir}", ctx.bin_dir.path) for k, v in plugin.env.items() } # Make compatible with aspect_rules_js js_binary rule and fix the following error: # FATAL: aspect_rules_js[js_binary]: BAZEL_BINDIR must be set in environment to the makevar # $(BINDIR) in js_binary build actions (which run in the execroot) so that build actions can # change directories to always run out of the root of the Bazel output tree. See # https://docs.bazel.build/versions/main/be/make-variables.html#predefined_variables. This # is automatically set by 'js_run_binary' # (https://github.com/aspect-build/rules_js/blob/main/docs/js_run_binary.md) which is the # recommended rule to use for using a js_binary as the tool of a build action. If this is # not a build action you can set the BAZEL_BINDIR to '.' instead to supress this error. For # more context on this design decision, please read the aspect_rules_js README # https://github.com/aspect-build/rules_js/tree/dbb5af0d2a9a2bb50e4cf4a96dbc582b27567155#running-nodejs-programs. plugin_env["BAZEL_BINDIR"] = ctx.bin_dir.path # Run protoc (https://bazel.build/rules/lib/actions#run) ctx.actions.run( mnemonic = mnemonic, executable = protoc, arguments = [args], inputs = cmd_inputs, tools = tools, outputs = plugin_protoc_outputs, env = plugin_env, use_default_shell_env = plugin.use_built_in_shell_environment, progress_message = "Compiling protoc outputs for {} plugin on target {}".format( plugin.name, ctx.label, ), toolchain = protoc_toolchain_label, ) # Build final output defaults for merged locations output_root = get_package_root(ctx) + "/" + ctx.label.name output_files = depset() output_dirs = depset() prefix_path = ctx.attr.prefix_path # Merge outputs if premerge_dirs: # If we have any output dirs specified, we declare a single output directory and merge all # files in one go using the merger tool. This is necessary to prevent path prefix conflicts if ctx.attr.output_mode != "PREFIXED": fail("Cannot use output_mode = {} when using plugins with directory outputs") # Declare single output directory dir_name = ctx.label.name if prefix_path: dir_name += "/" + prefix_path new_dir = ctx.actions.declare_directory(dir_name) output_dirs = depset(direct = [new_dir]) # Create paths manifest for merger, contaning both output directories and files, with lines # prefixed with 'D' and 'F' respectively merger_manifest_file = ctx.actions.declare_file(rel_premerge_root + "/" + "_plugin_merger_manifest.txt") ctx.actions.write(merger_manifest_file, "\n".join([ "D " + strip_path_prefix(d.path, premerge_root) # Path of the dir relative to the output root for d in premerge_dirs ] + [ "F " + strip_path_prefix(file.path, premerge_root) # Path of the file relative to the output root for file in premerge_files ])) # Apply merger ctx.actions.run( inputs = [merger_manifest_file] + premerge_dirs + premerge_files, outputs = [new_dir], arguments = [ merger_manifest_file.path, premerge_root, new_dir.path, ], progress_message = "Applying merger on target {}".format( ctx.label, ), executable = merger, ) else: # Otherwise, if we only have output files, build the output tree by # aggregating files into one directory output_files = [] for file in premerge_files: # Strip pre-merge root from file path path = strip_path_prefix(file.path, premerge_root) if ctx.attr.output_mode == "NO_PREFIX_FLAT": path = file.basename # Prepend prefix path if given if prefix_path: path = prefix_path + "/" + path # Select output location based on output mode # In PREFIXED mode we output to a directory named by the target label # In NO_PREFIX or NO_PREFIX_FLAT mode, we output directly to the package root if ctx.attr.output_mode == "PREFIXED": path = ctx.label.name + "/" + path # Copy file to output output_files.append(copy_file( ctx, file, path, )) output_files = depset(direct = output_files) # Create depset containing all outputs all_outputs = depset(direct = output_files.to_list() + output_dirs.to_list()) # Create default and proto compile providers return [ ProtoCompileInfo( label = ctx.label, output_root = output_root, output_files = output_files, output_dirs = output_dirs, ), DefaultInfo( files = all_outputs, runfiles = ctx.runfiles(transitive_files = all_outputs), ), ] rules_proto_grpc-5.8.0/internal/filter_files.bzl0000644000000000000000000000127200000000000020611 0ustar00rootroot"""Rule to filter files in the default output files by a file extension.""" def _filter_files_impl(ctx): """Filter the files in DefaultInfo.""" return [DefaultInfo( files = depset([ file for file in ctx.attr.target.files.to_list() if file.extension in ctx.attr.extensions ]), )] filter_files = rule( implementation = _filter_files_impl, attrs = { "target": attr.label( doc = "The source target to filter", mandatory = True, ), "extensions": attr.string_list( doc = "The extensions of the files to keep eg. ['h']", mandatory = True, ), }, ) rules_proto_grpc-5.8.0/internal/plugin.bzl0000644000000000000000000001100500000000000017433 0ustar00rootroot"""Plugin rules definition for rules_proto_grpc.""" load("//internal:providers.bzl", "ProtoPluginInfo") def _proto_plugin_impl(ctx): return [ ProtoPluginInfo( name = ctx.attr.name, label = ctx.label, tool = ctx.attr.tool, tool_provider = ctx.attr.tool[DefaultInfo] if ctx.attr.tool else None, protoc_plugin_name = ctx.attr.protoc_plugin_name, options = ctx.attr.options, outputs = ctx.attr.outputs, out = ctx.attr.out, output_directory = ctx.attr.output_directory, env = ctx.attr.env, extra_protoc_args = ctx.attr.extra_protoc_args, exclusions = ctx.attr.exclusions, data = ctx.files.data, use_built_in_shell_environment = ctx.attr.use_built_in_shell_environment, separate_options_flag = ctx.attr.separate_options_flag, empty_template = ctx.file.empty_template, quirks = ctx.attr.quirks, ), ] proto_plugin = rule( implementation = _proto_plugin_impl, attrs = { "tool": attr.label( doc = "The label of plugin binary target. Can be the output of select() to support multiple platforms. If absent, it is assumed the plugin is built-in to protoc itself and builtin_plugin_name will be used if available, otherwise the plugin name", cfg = "exec", allow_files = True, executable = True, ), "protoc_plugin_name": attr.string( doc = "The name used for the plugin binary on the protoc command line. Useful for targeting built-in plugins. Uses the plugin name when not set", ), "options": attr.string_list( doc = "A list of options to pass to the compiler for this plugin", ), "outputs": attr.string_list( doc = "Templates for output filenames generated on a per-proto basis, such as '{basename}_pb2.py'. The {basename} template variable will be replaced with the file basename and {protopath} with be replaced with the relative path to the .proto file after prefix mangling. If no template variables are present, the string is assumed to be a suffix of the file basename", ), "out": attr.string( doc = "Template for the output filename generated on a per-plugin basis; to be used in the value for --NAME-out=OUT. The {name} template variable will be replaced with the target name", ), "output_directory": attr.bool( doc = "Flag that indicates that the plugin should only output a directory. Used for plugins that have no direct mapping from source file name to output name. Cannot be used in conjunction with outputs or out", default = False, ), "env": attr.string_dict( doc = "A dictionary of key-value environment variables to use when invoking protoc for this plugin. Must be empty if use_built_in_shell_environment is true", default = {}, ), "extra_protoc_args": attr.string_list( doc = "A list of extra command line arguments to pass directly to protoc, not as plugin options", ), "exclusions": attr.string_list( doc = "Exclusion filters to apply when generating outputs with this plugin. Used to prevent generating files that are included in the protobuf library, for example. Can exclude either by proto name prefix or by proto folder prefix", ), "data": attr.label_list( doc = "Additional files required for running the plugin", allow_files = True, ), "use_built_in_shell_environment": attr.bool( doc = "Flag to indicate whether the tool should use the built in shell environment", default = True, ), "separate_options_flag": attr.bool( doc = "Flag to indicate if plugin options should be sent to protoc via the separate --{lang}_opts argument", default = False, ), "empty_template": attr.label( doc = "Template file to use to fill missing outputs when the fixer is required. If not provided, the fixer is not run", allow_single_file = True, ), "quirks": attr.string_list( doc = "List of quirks that toggle behaviours in compilation. The QUIRK_OUT_PASS_ROOT quirk enables passing the output directory to a plugin that outputs only a single file. The QUIRK_DIRECT_MODE quirk disables use of descriptors from proto_library and passes the files directly to protoc", ), }, ) rules_proto_grpc-5.8.0/internal/protoc.bzl0000644000000000000000000000642600000000000017456 0ustar00rootroot"""Protoc helper rules definition for rules_proto_grpc.""" def _path(file): return file.path def _short_path(file): return file.short_path def build_protoc_args( ctx, plugin, proto_infos, out_arg, extra_options = [], extra_protoc_args = [], short_paths = False): """ Build the args for a protoc invocation. This does not include the paths to the .proto files, which should be done external to this function. Args: ctx: The Bazel rule execution context object. plugin: The ProtoPluginInfo for the plugin to use. proto_infos: The list of ProtoInfo providers. out_arg: The path to provide as the output arg to protoc, usually the generation root dir. extra_options: An optional list of extra options to pass to the plugin. extra_protoc_args: An optional list of extra args to add to the command. short_paths: Whether to use the .short_path instead of .path when creating paths. The short_path is used when making a test/executable and referencing the runfiles. Returns: - The list of args. - The inputs required for the command. """ # Specify path getter get_path = _short_path if short_paths else _path # Build inputs list inputs = [] inputs += plugin.data # Get plugin name plugin_name = plugin.name if plugin.protoc_plugin_name: plugin_name = plugin.protoc_plugin_name # Build args args_list = [] # Load all descriptors (direct and transitive) and remove dupes descriptor_sets = depset([ descriptor_set for proto_info in proto_infos for descriptor_set in proto_info.transitive_descriptor_sets.to_list() ]).to_list() inputs += descriptor_sets # Add descriptors pathsep = ctx.configuration.host_path_separator args_list.append("--descriptor_set_in={}".format(pathsep.join( [get_path(f) for f in descriptor_sets], ))) # Add --plugin if not a built-in plugin if plugin.tool_provider: # If Windows, mangle the path. It's done a bit awkwardly with # `host_path_seprator` as there is no simple way to figure out what's # the current OS. if ctx.configuration.host_path_separator == ";": plugin_tool_path = get_path(plugin.tool_provider.files_to_run.executable).replace("/", "\\") else: plugin_tool_path = get_path(plugin.tool_provider.files_to_run.executable) args_list.append("--plugin=protoc-gen-{}={}".format(plugin_name, plugin_tool_path)) # Add plugin --*_out/--*_opt args plugin_options = list(plugin.options) plugin_options.extend(extra_options) if plugin_options: opts_str = ",".join( [option.replace("{name}", ctx.label.name) for option in plugin_options], ) if plugin.separate_options_flag: args_list.append("--{}_opt={}".format(plugin_name, opts_str)) else: out_arg = "{}:{}".format(opts_str, out_arg) args_list.append("--{}_out={}".format(plugin_name, out_arg)) # Add any extra protoc args provided or that plugin has args_list.extend(extra_protoc_args) if plugin.extra_protoc_args: args_list.extend(plugin.extra_protoc_args) return args_list, inputs rules_proto_grpc-5.8.0/internal/providers.bzl0000644000000000000000000000507200000000000020161 0ustar00rootroot"""Provider definitions for rules_proto_grpc.""" ProtoPluginInfo = provider( doc = "Information about the behaviour and location of a protoc plugin", fields = { "name": "The proto plugin name", "label": "The label used to define the plugin", "options": "A list of options to pass to the compiler for this plugin", "extra_protoc_args": "A list of extra args to pass directly to protoc, not as plugin options", "outputs": "Output filenames generated on a per-proto basis. Example: '{basename}_pb2.py", "out": "Output filename generated on a per-plugin basis; to be used in the value for --NAME-out=OUT", "output_directory": "Flag that indicates that the plugin should just output a directory. Used for plugins that have no direct mapping from source file name to output name. Cannot be used in conjunction with outputs or out", "tool": "The plugin binary. If absent, it is assumed the plugin is built-in to protoc itself and plugin_name will be used if available, otherwise the plugin name", "tool_provider": "The plugin DefaultInfo, if available. Encapsulates the executable and its runfiles", "use_built_in_shell_environment": "Whether the tool should use the built in shell environment or not", "env": "Sets the dictionary of environment variables to use when invoking protoc. Must be None if use_built_in_shell_environment is true.", "protoc_plugin_name": "The name used for the plugin binary on the protoc command line. Useful for targeting built-in plugins. Uses plugin name when not set", "exclusions": "Exclusion filters to apply when generating outputs with this plugin. Used to prevent generating files that are included in the protobuf library, for example. Can exclude either by proto name prefix or by proto folder prefix", "data": "Additional files required for running the plugin", "separate_options_flag": "Flag to indicate if plugin options should be sent via the --{lang}_opts flag", "empty_template": "Template file to use to fill missing outputs", "quirks": "List of plugin quirks that toggle behaviours in compilation", }, ) ProtoCompileInfo = provider( doc = "Information about the files and directories created by one or many protoc invocations", fields = { "label": "label object", "output_root": "The roots of the generated files and directories, as a string", "output_files": "The files generated by this rule, as a depset", "output_dirs": "The directories generated by this rule, as a depset", }, ) rules_proto_grpc-5.8.0/platforms/0000755000000000000000000000000000000000000015622 5ustar00rootrootrules_proto_grpc-5.8.0/platforms/BUILD.bazel0000644000000000000000000000311400000000000017477 0ustar00rootroot# This is required because the @platforms repo does not contain the product of os and cpu and we # need to select protoc plugins based on these two together. See Buf or doc module BUILD files for # example # # See https://github.com/bazelbuild/platforms/issues/36 # and https://github.com/bazelbuild/bazel/issues/8622 # and https://cs.opensource.google/bazel/bazel/+/master:src/conditions/BUILD load("@bazel_skylib//lib:selects.bzl", "selects") selects.config_setting_group( name = "darwin_arm64", match_all = [ "@platforms//os:macos", "@platforms//cpu:arm64", ], visibility = ["//visibility:public"], ) selects.config_setting_group( name = "darwin_x86_64", match_all = [ "@platforms//os:macos", "@platforms//cpu:x86_64", ], visibility = ["//visibility:public"], ) selects.config_setting_group( name = "linux_aarch64", match_all = [ "@platforms//os:linux", "@platforms//cpu:aarch64", ], visibility = ["//visibility:public"], ) selects.config_setting_group( name = "linux_x86_64", match_all = [ "@platforms//os:linux", "@platforms//cpu:x86_64", ], visibility = ["//visibility:public"], ) selects.config_setting_group( name = "windows_arm64", match_all = [ "@platforms//os:windows", "@platforms//cpu:arm64", ], visibility = ["//visibility:public"], ) selects.config_setting_group( name = "windows_x86_64", match_all = [ "@platforms//os:windows", "@platforms//cpu:x86_64", ], visibility = ["//visibility:public"], ) rules_proto_grpc-5.8.0/tools/0000755000000000000000000000000000000000000014753 5ustar00rootrootrules_proto_grpc-5.8.0/tools/fixer/0000755000000000000000000000000000000000000016070 5ustar00rootrootrules_proto_grpc-5.8.0/tools/fixer/BUILD.bazel0000644000000000000000000000041500000000000017746 0ustar00rootrootload("@rules_rust//rust:defs.bzl", "rust_binary", "rust_clippy") rust_binary( name = "fixer", srcs = ["main.rs"], visibility = ["//visibility:public"], ) rust_clippy( name = "fixer_clippy", testonly = True, deps = [ ":fixer", ], ) rules_proto_grpc-5.8.0/tools/fixer/README.md0000644000000000000000000000114100000000000017344 0ustar00rootroot# Fixer This is a simple executable that fills in missing files from plugins that have non-deterministic outputs. This is particularly useful for gRPC plugins that do not make an output file when the proto file does not contain a service and also cannot be fixed by using output_directory. As a result, this tool is the option of last resort to fix a plugin that does not behave well with Bazel. This executable receives the directory of generated plugin outputs, a file containing the expected file paths and a path to an empty file template. Any missing files will be filled with the empty file template. rules_proto_grpc-5.8.0/tools/fixer/main.rs0000644000000000000000000001112600000000000017363 0ustar00rootrootuse std::collections::HashMap; use std::env; use std::fs; use std::path::{Path, PathBuf}; use std::process; fn main() { // Parse args let args: Vec = env::args().collect(); if args.len() != 5 { eprintln!("Expected 4 arguments in order: