The internal design is consistent with other <lang>_proto_library rules. rust_proto_library attaches rust_proto_library_aspect on its `deps` attribute. The aspect traverses the dependency, and when it visits proto_library (detected by ProtoInfo provider) it registers 2 actions: 1) to run protoc with Rust backend to emit gencode 2) to compile the gencode using Rustc Action (2) gets the Rust proto runtime as an input as well. Coming in a followup is support and test coverage for proto_library.deps. PiperOrigin-RevId: 514521285pull/12156/head
parent
77e3f2c38d
commit
3dc546daff
13 changed files with 453 additions and 15 deletions
@ -0,0 +1,19 @@ |
||||
# Protobuf Rust runtime packages. |
||||
|
||||
load("@rules_rust//rust:defs.bzl", "rust_library") |
||||
|
||||
package(default_visibility = ["//src/google/protobuf:__subpackages__"]) |
||||
|
||||
rust_library( |
||||
name = "protobuf", |
||||
srcs = ["lib.rs"], |
||||
) |
||||
|
||||
# TODO(b/270125787): Move to the right location once rust_proto_library is no longer experimental. |
||||
proto_lang_toolchain( |
||||
name = "proto_lang_toolchain", |
||||
command_line = "--rust_out=experimental-codegen=enabled:$(OUT)", |
||||
progress_message = "Generating Rust proto_library %{label}", |
||||
runtime = ":protobuf", |
||||
visibility = ["//visibility:public"], |
||||
) |
@ -1,10 +0,0 @@ |
||||
# Protobuf Rust runtime packages. |
||||
|
||||
load("@rules_rust//rust:defs.bzl", "rust_library") |
||||
|
||||
package(default_visibility = ["//src/google/protobuf:__subpackages__"]) |
||||
|
||||
rust_library( |
||||
name = "protobuf", |
||||
srcs = ["lib.rs"], |
||||
) |
@ -0,0 +1,242 @@ |
||||
"""This file implements an experimental, do-not-use-kind of rust_proto_library. |
||||
|
||||
Disclaimer: This project is experimental, under heavy development, and should not |
||||
be used yet.""" |
||||
|
||||
# buildifier: disable=bzl-visibility |
||||
load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVariantInfo") |
||||
|
||||
# buildifier: disable=bzl-visibility |
||||
load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action") |
||||
load("@rules_rust//rust:defs.bzl", "rust_common") |
||||
|
||||
proto_common = proto_common_do_not_use |
||||
|
||||
RustProtoInfo = provider( |
||||
doc = "Rust protobuf provider info", |
||||
fields = { |
||||
"dep_variant_info": "DepVariantInfo for the compiled Rust gencode (also covers its " + |
||||
"transitive dependencies)", |
||||
}, |
||||
) |
||||
|
||||
def _generate_rust_gencode( |
||||
ctx, |
||||
proto_info, |
||||
proto_lang_toolchain): |
||||
"""Generates Rust gencode |
||||
|
||||
This function uses proto_common APIs and a ProtoLangToolchain to register an action |
||||
that invokes protoc with the right flags. |
||||
Args: |
||||
ctx (RuleContext): current rule context |
||||
proto_info (ProtoInfo): ProtoInfo of the proto_library target for which we are generating |
||||
gencode |
||||
proto_lang_toolchain (ProtoLangToolchainInfo): proto lang toolchain for Rust |
||||
Returns: |
||||
rs_outputs ([File]): generated Rust source files |
||||
""" |
||||
actions = ctx.actions |
||||
rs_outputs = proto_common.declare_generated_files( |
||||
actions = actions, |
||||
proto_info = proto_info, |
||||
extension = ".pb.rs", |
||||
) |
||||
|
||||
proto_common.compile( |
||||
actions = ctx.actions, |
||||
proto_info = proto_info, |
||||
generated_files = rs_outputs, |
||||
proto_lang_toolchain_info = proto_lang_toolchain, |
||||
plugin_output = ctx.bin_dir.path, |
||||
) |
||||
return rs_outputs |
||||
|
||||
def _get_crate_info(providers): |
||||
for provider in providers: |
||||
if hasattr(provider, "name"): |
||||
return provider |
||||
fail("Couldn't find a CrateInfo in the list of providers") |
||||
|
||||
def _get_dep_info(providers): |
||||
for provider in providers: |
||||
if hasattr(provider, "direct_crates"): |
||||
return provider |
||||
fail("Couldn't find a DepInfo in the list of providers") |
||||
|
||||
def _get_cc_info(providers): |
||||
for provider in providers: |
||||
if hasattr(provider, "linking_context"): |
||||
return provider |
||||
fail("Couldn't find a CcInfo in the list of providers") |
||||
|
||||
def _compile_rust(ctx, attr, src, extra_srcs, deps): |
||||
"""Compiles a Rust source file. |
||||
|
||||
Eventually this function could be upstreamed into rules_rust and be made present in rust_common. |
||||
|
||||
Args: |
||||
ctx (RuleContext): The rule context. |
||||
attr (Attrs): The current rule's attributes (`ctx.attr` for rules, `ctx.rule.attr` for aspects) |
||||
src (File): The crate root source file to be compiled. |
||||
extra_srcs ([File]): Additional source files to include in the crate. |
||||
deps (List[DepVariantInfo]): A list of dependencies needed. |
||||
|
||||
Returns: |
||||
A DepVariantInfo provider. |
||||
""" |
||||
toolchain = ctx.toolchains["@rules_rust//rust:toolchain"] |
||||
output_hash = repr(hash(src.path)) |
||||
|
||||
# TODO(b/270124215): Use the import! macro once available |
||||
crate_name = ctx.label.name.replace("-", "_") |
||||
|
||||
lib_name = "{prefix}{name}-{lib_hash}{extension}".format( |
||||
prefix = "lib", |
||||
name = crate_name, |
||||
lib_hash = output_hash, |
||||
extension = ".rlib", |
||||
) |
||||
|
||||
rmeta_name = "{prefix}{name}-{lib_hash}{extension}".format( |
||||
prefix = "lib", |
||||
name = crate_name, |
||||
lib_hash = output_hash, |
||||
extension = ".rmeta", |
||||
) |
||||
|
||||
lib = ctx.actions.declare_file(lib_name) |
||||
rmeta = ctx.actions.declare_file(rmeta_name) |
||||
|
||||
providers = rustc_compile_action( |
||||
ctx = ctx, |
||||
attr = attr, |
||||
toolchain = toolchain, |
||||
crate_info = rust_common.create_crate_info( |
||||
name = crate_name, |
||||
type = "rlib", |
||||
root = src, |
||||
srcs = depset([src] + extra_srcs), |
||||
deps = depset(deps), |
||||
proc_macro_deps = depset([]), |
||||
aliases = {}, |
||||
output = lib, |
||||
metadata = rmeta, |
||||
edition = "2021", |
||||
is_test = False, |
||||
rustc_env = {}, |
||||
compile_data = depset([]), |
||||
compile_data_targets = depset([]), |
||||
owner = ctx.label, |
||||
), |
||||
output_hash = output_hash, |
||||
) |
||||
|
||||
return DepVariantInfo( |
||||
crate_info = _get_crate_info(providers), |
||||
dep_info = _get_dep_info(providers), |
||||
cc_info = _get_cc_info(providers), |
||||
build_info = None, |
||||
) |
||||
|
||||
def _rust_proto_aspect_impl(target, ctx): |
||||
if ProtoInfo not in target: |
||||
return None |
||||
|
||||
proto_lang_toolchain = ctx.attr._proto_lang_toolchain[proto_common.ProtoLangToolchainInfo] |
||||
|
||||
gencode = _generate_rust_gencode(ctx, target[ProtoInfo], proto_lang_toolchain) |
||||
|
||||
runtime = proto_lang_toolchain.runtime |
||||
dep_variant_info_for_runtime = DepVariantInfo( |
||||
crate_info = runtime[CrateInfo] if CrateInfo in runtime else None, |
||||
dep_info = runtime[DepInfo] if DepInfo in runtime else None, |
||||
cc_info = runtime[CcInfo] if CcInfo in runtime else None, |
||||
build_info = None, |
||||
) |
||||
|
||||
dep_variant_info = _compile_rust( |
||||
ctx = ctx, |
||||
attr = ctx.rule.attr, |
||||
src = gencode[0], |
||||
extra_srcs = gencode[1:], |
||||
deps = [dep_variant_info_for_runtime], |
||||
) |
||||
return [RustProtoInfo( |
||||
dep_variant_info = dep_variant_info, |
||||
)] |
||||
|
||||
rust_proto_library_aspect = aspect( |
||||
implementation = _rust_proto_aspect_impl, |
||||
attr_aspects = ["deps"], |
||||
attrs = { |
||||
"_cc_toolchain": attr.label( |
||||
doc = ( |
||||
"In order to use find_cc_toolchain, your rule has to depend " + |
||||
"on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " + |
||||
"docs for details." |
||||
), |
||||
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
||||
), |
||||
"_collect_cc_coverage": attr.label( |
||||
default = Label("@rules_rust//util:collect_coverage"), |
||||
executable = True, |
||||
cfg = "exec", |
||||
), |
||||
"_error_format": attr.label( |
||||
default = Label("@rules_rust//:error_format"), |
||||
), |
||||
"_extra_exec_rustc_flag": attr.label( |
||||
default = Label("@rules_rust//:extra_exec_rustc_flag"), |
||||
), |
||||
"_extra_exec_rustc_flags": attr.label( |
||||
default = Label("@rules_rust//:extra_exec_rustc_flags"), |
||||
), |
||||
"_extra_rustc_flag": attr.label( |
||||
default = Label("@rules_rust//:extra_rustc_flag"), |
||||
), |
||||
"_extra_rustc_flags": attr.label( |
||||
default = Label("@rules_rust//:extra_rustc_flags"), |
||||
), |
||||
"_process_wrapper": attr.label( |
||||
doc = "A process wrapper for running rustc on all platforms.", |
||||
default = Label("@rules_rust//util/process_wrapper"), |
||||
executable = True, |
||||
allow_single_file = True, |
||||
cfg = "exec", |
||||
), |
||||
"_proto_lang_toolchain": attr.label( |
||||
default = Label("//rust:proto_lang_toolchain"), |
||||
), |
||||
}, |
||||
fragments = ["cpp"], |
||||
host_fragments = ["cpp"], |
||||
toolchains = [ |
||||
str(Label("@rules_rust//rust:toolchain")), |
||||
"@bazel_tools//tools/cpp:toolchain_type", |
||||
], |
||||
incompatible_use_toolchain_transition = True, |
||||
) |
||||
|
||||
def _rust_proto_library_impl(ctx): |
||||
deps = ctx.attr.deps |
||||
if not deps: |
||||
fail("Exactly 1 dependency in `deps` attribute expected, none were provided.") |
||||
if len(deps) > 1: |
||||
fail("Exactly 1 dependency in `deps` attribute expected, too many were provided.") |
||||
|
||||
dep = deps[0] |
||||
rust_proto_info = dep[RustProtoInfo] |
||||
dep_variant_info = rust_proto_info.dep_variant_info |
||||
return [dep_variant_info.crate_info, dep_variant_info.dep_info, dep_variant_info.cc_info] |
||||
|
||||
rust_proto_library = rule( |
||||
implementation = _rust_proto_library_impl, |
||||
attrs = { |
||||
"deps": attr.label_list( |
||||
mandatory = True, |
||||
providers = [ProtoInfo], |
||||
aspects = [rust_proto_library_aspect], |
||||
), |
||||
}, |
||||
) |
@ -0,0 +1,16 @@ |
||||
load("//rust:defs.bzl", "rust_proto_library") |
||||
load("@rules_rust//rust:defs.bzl", "rust_test") |
||||
|
||||
rust_proto_library( |
||||
name = "hello_world_rs_proto", |
||||
testonly = True, |
||||
deps = ["//third_party/protobuf:unittest_proto"], |
||||
) |
||||
|
||||
rust_test( |
||||
name = "hello_world_test", |
||||
srcs = ["hello_world_test.rs"], |
||||
# TODO(b/270274576): Enable testing on arm once we have a Rust Arm toolchain. |
||||
tags = ["not_build:arm"], |
||||
deps = [":hello_world_rs_proto"], |
||||
) |
@ -0,0 +1,35 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
fn main() { |
||||
// This is currently just a smoke test checking that we can generate gencode, compile it, and
|
||||
// link the test binary.
|
||||
let _test_all_types: unittest_proto::TestAllTypes; |
||||
} |
@ -0,0 +1,3 @@ |
||||
load(":rust_proto_library_unit_test.bzl", "rust_proto_library_unit_test") |
||||
|
||||
rust_proto_library_unit_test(name = "rust_proto_library_unit_test") |
@ -0,0 +1,19 @@ |
||||
"""Support for rust_proto_library_aspect unit-tests.""" |
||||
|
||||
load("//rust:defs.bzl", "RustProtoInfo", "rust_proto_library_aspect") |
||||
|
||||
ActionsInfo = provider( |
||||
doc = ("A provider that exposes what actions were registered by rust_proto_library_aspect " + |
||||
"on proto_libraries."), |
||||
fields = {"actions": "List[Action]: actions registered on proto_libraries."}, |
||||
) |
||||
|
||||
def _attach_aspect_impl(ctx): |
||||
return [ctx.attr.dep[RustProtoInfo], ActionsInfo(actions = ctx.attr.dep.actions)] |
||||
|
||||
attach_aspect = rule( |
||||
implementation = _attach_aspect_impl, |
||||
attrs = { |
||||
"dep": attr.label(aspects = [rust_proto_library_aspect]), |
||||
}, |
||||
) |
@ -0,0 +1,61 @@ |
||||
"""This module contains unit tests for rust_proto_library and its aspect.""" |
||||
|
||||
load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") |
||||
load(":defs.bzl", "ActionsInfo", "attach_aspect") |
||||
|
||||
def _find_action_with_mnemonic(actions, mnemonic): |
||||
action = [a for a in actions if a.mnemonic == mnemonic] |
||||
if not action: |
||||
fail("Couldn't find action with mnemonic {} among {}".format(mnemonic, actions)) |
||||
return action[0] |
||||
|
||||
def _find_rust_lib_input(inputs, target_name): |
||||
inputs = inputs.to_list() |
||||
input = [i for i in inputs if i.basename.startswith("lib" + target_name) and |
||||
(i.basename.endswith(".rlib") or i.basename.endswith(".rmeta"))] |
||||
if not input: |
||||
fail("Couldn't find lib{}-<hash>.rlib or lib{}-<hash>.rmeta among {}".format( |
||||
target_name, |
||||
target_name, |
||||
[i.basename for i in inputs], |
||||
)) |
||||
return input[0] |
||||
|
||||
def _rust_compilation_action_has_runtime_as_input_test_impl(ctx): |
||||
env = analysistest.begin(ctx) |
||||
target_under_test = analysistest.target_under_test(env) |
||||
actions = target_under_test[ActionsInfo].actions |
||||
rustc_action = _find_action_with_mnemonic(actions, "Rustc") |
||||
_find_rust_lib_input(rustc_action.inputs, "protobuf") |
||||
asserts.true(env, rustc_action.outputs.to_list()[0].path.endswith(".rlib")) |
||||
|
||||
return analysistest.end(env) |
||||
|
||||
rust_compilation_action_has_runtime_as_input_test = analysistest.make( |
||||
_rust_compilation_action_has_runtime_as_input_test_impl, |
||||
) |
||||
|
||||
def _test_rust_compilation_action_has_runtime_as_input(): |
||||
native.proto_library(name = "some_proto", srcs = ["some_proto.proto"]) |
||||
attach_aspect(name = "some_proto_with_aspect", dep = ":some_proto") |
||||
|
||||
rust_compilation_action_has_runtime_as_input_test( |
||||
name = "rust_compilation_action_has_runtime_as_input_test", |
||||
target_under_test = ":some_proto_with_aspect", |
||||
# TODO(b/270274576): Enable testing on arm once we have a Rust Arm toolchain. |
||||
tags = ["not_build:arm"], |
||||
) |
||||
|
||||
def rust_proto_library_unit_test(name): |
||||
"""Sets up rust_proto_library_unit_test test suite. |
||||
|
||||
Args: |
||||
name: name of the test suite""" |
||||
_test_rust_compilation_action_has_runtime_as_input() |
||||
|
||||
native.test_suite( |
||||
name = name, |
||||
tests = [ |
||||
":rust_compilation_action_has_runtime_as_input_test", |
||||
], |
||||
) |
@ -0,0 +1,39 @@ |
||||
// Protocol Buffers - Google's data interchange format |
||||
// Copyright 2008 Google Inc. All rights reserved. |
||||
// https://developers.google.com/protocol-buffers/ |
||||
// |
||||
// Redistribution and use in source and binary forms, with or without |
||||
// modification, are permitted provided that the following conditions are |
||||
// met: |
||||
// |
||||
// * Redistributions of source code must retain the above copyright |
||||
// notice, this list of conditions and the following disclaimer. |
||||
// * Redistributions in binary form must reproduce the above |
||||
// copyright notice, this list of conditions and the following disclaimer |
||||
// in the documentation and/or other materials provided with the |
||||
// distribution. |
||||
// * Neither the name of Google Inc. nor the names of its |
||||
// contributors may be used to endorse or promote products derived from |
||||
// this software without specific prior written permission. |
||||
// |
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
// This file exists because of the design of the Starlark unit testing |
||||
// framework. We call these tests unittests, but Blaze still sees them as full |
||||
// builds. And while Blaze doesn't build the test-setup targets unless it really |
||||
// needs to, it checks that all input files are present. Therefore, we need |
||||
// these "empty" files to be present. |
||||
|
||||
syntax = "proto2"; |
||||
|
||||
package third_party_protobuf_rust_test_rust_proto_library_unit_test; |
Loading…
Reference in new issue