From ec4947064a63eeabc359fe4f0bc950698fdfad89 Mon Sep 17 00:00:00 2001 From: satreix Date: Mon, 6 May 2024 20:55:15 +0100 Subject: [PATCH] feat: Add C++ grpc example --- .bazelrc | 4 + src/cpp/hello_world/greet/BUILD.bazel | 5 +- src/cpp/hello_world/server/BUILD.bazel | 33 +++++++ src/cpp/hello_world/server/server.cc | 45 +++++++++ src/cpp/hello_world/server/server_test.go | 111 ++++++++++++++++++++++ src/proto/helloworld/BUILD.bazel | 15 +++ 6 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/cpp/hello_world/server/BUILD.bazel create mode 100644 src/cpp/hello_world/server/server.cc create mode 100644 src/cpp/hello_world/server/server_test.go diff --git a/.bazelrc b/.bazelrc index c8218794..0856798c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,5 +1,9 @@ build --cxxopt='-std=c++17' +# On macOS with an installed oppenssl, this prevent Bazel from looking in /usr/local/include and failing the boringssl compile. +# This does on the other hand brak the python interpreter in rules_python +# build --sandbox_block_path=/usr/local + # Ensure rules_haskell picks up the correct toolchain on Mac. build --action_env=BAZEL_USE_CPP_ONLY_TOOLCHAIN=1 diff --git a/src/cpp/hello_world/greet/BUILD.bazel b/src/cpp/hello_world/greet/BUILD.bazel index 4376f5e5..07fcff52 100644 --- a/src/cpp/hello_world/greet/BUILD.bazel +++ b/src/cpp/hello_world/greet/BUILD.bazel @@ -4,7 +4,10 @@ cc_library( name = "greet", srcs = ["greet.cc"], hdrs = ["greet.h"], - visibility = ["//src/cpp/hello_world:__pkg__"], + visibility = [ + "//src/cpp/hello_world:__pkg__", + "//src/cpp/hello_world/server:__pkg__", + ], deps = ["@fmt"], ) diff --git a/src/cpp/hello_world/server/BUILD.bazel b/src/cpp/hello_world/server/BUILD.bazel new file mode 100644 index 00000000..ec8588a0 --- /dev/null +++ b/src/cpp/hello_world/server/BUILD.bazel @@ -0,0 +1,33 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@io_bazel_rules_go//go:def.bzl", "go_test") +load("@rules_cc//cc:defs.bzl", "cc_binary") + +cc_binary( + name = "server", + srcs = ["server.cc"], + deps = [ + "//src/cpp/hello_world/greet", + "//src/proto/helloworld:helloworld_cc_grpc", + "@gflags", + "@grpc//:grpc++", + "@grpc//:grpc++_reflection", + ], +) + +build_test( + name = "server_build_test", + targets = [":server"], +) + +go_test( + name = "server_test", + size = "medium", + timeout = "short", + srcs = ["server_test.go"], + data = [":server"], + deps = [ + "//src/proto/helloworld", + "@io_bazel_rules_go//go/tools/bazel:go_default_library", + "@org_golang_google_grpc//:go_default_library", + ], +) diff --git a/src/cpp/hello_world/server/server.cc b/src/cpp/hello_world/server/server.cc new file mode 100644 index 00000000..fd473547 --- /dev/null +++ b/src/cpp/hello_world/server/server.cc @@ -0,0 +1,45 @@ +#include + +#include "gflags/gflags.h" +#include +#include +#include + +#include "src/proto/helloworld/helloworld.grpc.pb.h" +#include "src/cpp/hello_world/greet/greet.h" + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::Status; +using helloworld::HelloReply; +using helloworld::HelloRequest; +using helloworld::Greeter; + +DEFINE_string(address, "0.0.0.0:50051", "address to bind to"); + +class GreeterServiceImpl final : public Greeter::Service { + Status SayHello(ServerContext *context, + const HelloRequest *request, + HelloReply *reply) override { + reply->set_message(greet::greet(request->name())); + return Status::OK; + } +}; + +int +main(int argc, char **argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + GreeterServiceImpl service; + + grpc::EnableDefaultHealthCheckService(true); + grpc::reflection::InitProtoReflectionServerBuilderPlugin(); + ServerBuilder builder; + builder.AddListeningPort(FLAGS_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + std::unique_ptr server(builder.BuildAndStart()); + std::cerr << "Server listening on " << FLAGS_address << std::endl; + server->Wait(); + return 0; +} diff --git a/src/cpp/hello_world/server/server_test.go b/src/cpp/hello_world/server/server_test.go new file mode 100644 index 00000000..722cebfa --- /dev/null +++ b/src/cpp/hello_world/server/server_test.go @@ -0,0 +1,111 @@ +package main_test + +import ( + "context" + "fmt" + "log" + "net" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/bazelbuild/rules_go/go/tools/bazel" + "github.com/satreix/everest/src/proto/helloworld" + "google.golang.org/grpc" +) + +func TestEnd2End(t *testing.T) { + const iface = "127.0.0.1" + port, err := freeTCPPort("tcp", iface) + if err != nil { + t.Fatalf("could not get a free port: %s", err) + } + addr := fmt.Sprintf("%s:%d", iface, port) + t.Logf("addr: %s", addr) + + serverBin, err := findBinary("//src/cpp/hello_world/server", "server") + if err != nil { + t.Fatalf("error finding server: %s", err) + } + t.Log("server: " + serverBin) + + serverCmd := exec.Command(serverBin, fmt.Sprintf("--address=%s", addr)) + if err := serverCmd.Start(); err != nil { + t.Fatalf("error starting server: %s", err) + } + defer serverCmd.Process.Kill() + t.Logf("serverCmd: %s", strings.Join(serverCmd.Args, " ")) + + // FIXME make this not time based but instead retry the assertion + time.Sleep(2 * time.Second) + + conn, err := grpc.Dial(addr, grpc.WithInsecure()) + if err != nil { + t.Fatalf("Failed to connect: %v", err) + } + defer conn.Close() + + client := helloworld.NewGreeterClient(conn) + + resp, err := client.SayHello(context.Background(), &helloworld.HelloRequest{ + Name: "Alice", + }) + if err != nil { + t.Fatalf("SayHello failed: %v", err) + } + + expectedMessage := "Hello, Alice!" + if resp.Message != expectedMessage { + t.Fatalf("Unexpected response: got %q, want %q", resp.Message, expectedMessage) + } +} + +func findBinary(pkg, name string) (string, error) { + // workaround for external targets + if strings.HasPrefix(pkg, "@") { + pkg = "external/" + strings.TrimPrefix(pkg, "@") + } + pkg = strings.TrimPrefix(pkg, "//") + + rfs, err := bazel.ListRunfiles() + if err != nil { + return "", err + } + for idx, rf := range rfs { + if rf.ShortPath == filepath.Join(pkg, name) { + return rf.Path, nil + } + + log.Printf("%d. %#v", idx, rf) + } + + bin, found := bazel.FindBinary(pkg, name) + if !found { + return "", fmt.Errorf("couldn't find binary for %s:%s", pkg, name) + } + + fi, err := os.Stat(bin) + if err != nil { + panic(err.Error()) + } + + if fi.Mode().IsDir() { + return bin + "/" + runtime.GOOS + "_" + runtime.GOARCH + "_stripped/" + name, nil + } + + return bin, nil +} + +func freeTCPPort(network, iface string) (int, error) { + l, err := net.Listen(network, iface+":0") + if err != nil { + return 0, err + } + defer l.Close() + + return l.Addr().(*net.TCPAddr).Port, nil +} diff --git a/src/proto/helloworld/BUILD.bazel b/src/proto/helloworld/BUILD.bazel index 843e96cc..68cc85e7 100644 --- a/src/proto/helloworld/BUILD.bazel +++ b/src/proto/helloworld/BUILD.bazel @@ -1,6 +1,8 @@ +load("@grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library") load("@grpc-java//:java_grpc_library.bzl", "java_grpc_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("@rules_java//java:defs.bzl", "java_proto_library") load("@rules_proto//proto:defs.bzl", "proto_library") load("@rules_rust//proto/protobuf:defs.bzl", "rust_grpc_library") @@ -14,6 +16,19 @@ proto_library( visibility = ["//visibility:public"], ) +cc_proto_library( + name = "helloworld_cc_proto", + deps = [":helloworld_proto"], +) + +cc_grpc_library( + name = "helloworld_cc_grpc", + srcs = [":helloworld_proto"], + grpc_only = True, + visibility = ["//src/cpp/hello_world/server:__pkg__"], + deps = [":helloworld_cc_proto"], +) + go_proto_library( name = "helloworld_go_proto", compilers = ["@io_bazel_rules_go//proto:go_grpc"],