diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index b87b4e9745..e95d974e15 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -174,6 +174,7 @@ cc_dist_library( "//src/google/protobuf:arena_align", "//src/google/protobuf:cmake_wkt_cc_proto", "//src/google/protobuf/compiler:importer", + "//src/google/protobuf/io/cpp_utils:ifndef_guard", "//src/google/protobuf/json", "//src/google/protobuf/util:delimited_message_util", "//src/google/protobuf/util:differencer", diff --git a/src/google/protobuf/io/cpp_utils/BUILD.bazel b/src/google/protobuf/io/cpp_utils/BUILD.bazel new file mode 100644 index 0000000000..87f603dec9 --- /dev/null +++ b/src/google/protobuf/io/cpp_utils/BUILD.bazel @@ -0,0 +1,58 @@ +# Utilities for generating C++ code + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix") +load("//build_defs:cpp_opts.bzl", "COPTS") + +package( + default_visibility = ["//visibility:public"], +) + +cc_library( + name = "ifndef_guard", + srcs = ["ifndef_guard.cc"], + hdrs = ["ifndef_guard.h"], + copts = COPTS, + strip_include_prefix = "/src", + deps = [ + "//src/google/protobuf/io:printer", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/log:die_if_null", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_test( + name = "ifndef_guard_unittest", + srcs = ["ifndef_guard_unittest.cc"], + deps = [ + ":ifndef_guard", + "//src/google/protobuf/io", + "//src/google/protobuf/io:printer", + "@com_google_absl//absl/log:absl_check", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:optional", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +################################################################################ +# Distribution packaging +################################################################################ + +pkg_files( + name = "dist_files", + srcs = glob(["**/*"]), + strip_prefix = strip_prefix.from_root(""), + visibility = ["//src:__pkg__"], +) + +filegroup( + name = "test_srcs", + srcs = glob([ + "*unittest.cc", + ]), + visibility = ["//pkg:__pkg__"], +) diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard.cc b/src/google/protobuf/io/cpp_utils/ifndef_guard.cc new file mode 100644 index 0000000000..2960689e48 --- /dev/null +++ b/src/google/protobuf/io/cpp_utils/ifndef_guard.cc @@ -0,0 +1,71 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "google/protobuf/io/cpp_utils/ifndef_guard.h" + +#include <string> + +#include "absl/functional/any_invocable.h" +#include "absl/log/die_if_null.h" +#include "absl/strings/ascii.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "google/protobuf/io/printer.h" + +namespace google { +namespace protobuf { +namespace io { +namespace cpp { + +namespace { + +std::string MakeIfdefGuardIdentifier(const absl::string_view header_path) { + return absl::StrCat(absl::AsciiStrToUpper(absl::StrReplaceAll(header_path, + { + {"/", "_"}, + {".", "_"}, + })), + "_"); +} + +} // namespace + +IfdefGuardPrinter::IfdefGuardPrinter(google::protobuf::io::Printer* const p, + const absl::string_view filename) + : IfdefGuardPrinter(p, filename, MakeIfdefGuardIdentifier) {} + +IfdefGuardPrinter::IfdefGuardPrinter( + google::protobuf::io::Printer* const p, const absl::string_view filename, + absl::AnyInvocable<std::string(absl::string_view)> make_ifdef_identifier) + : p_(ABSL_DIE_IF_NULL(p)), + ifdef_identifier_(make_ifdef_identifier(filename)) { + // We can't use variable substitution, because we don't know what delimiter + // to use. + p->Print(absl::Substitute( + R"(#ifndef $0 +#define $0 + +)", + ifdef_identifier_)); +} + +IfdefGuardPrinter::~IfdefGuardPrinter() { + // We can't use variable substitution, because we don't know what delimiter + // to use. + p_->Print(absl::Substitute( + R"( +#endif // $0 +)", + ifdef_identifier_)); +} + +} // namespace cpp +} // namespace io +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard.h b/src/google/protobuf/io/cpp_utils/ifndef_guard.h new file mode 100644 index 0000000000..e82548ee7c --- /dev/null +++ b/src/google/protobuf/io/cpp_utils/ifndef_guard.h @@ -0,0 +1,61 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// An RAII type for printing an ifdef guard. +// +// This can be used to ensure that appropriate ifdef guards are applied in +// a generated header file. +// +// Example: +// { +// Printer printer(output_stream.get(), '$'); +// const IfdefGuardPrinter ifdef_guard(&printer, output_path); +// // #ifdef guard will be emitted here +// ... +// // #endif will be emitted here +// } +// +// By default, the filename will be converted to a macro by substituting '/' and +// '.' characters with '_'. If a different transformation is required, an +// optional transformation function can be provided. + +#ifndef GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__ +#define GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__ + +#include <string> + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "google/protobuf/io/printer.h" + +namespace google { +namespace protobuf { +namespace io { +namespace cpp { + +class IfdefGuardPrinter final { + public: + explicit IfdefGuardPrinter(google::protobuf::io::Printer* p, + absl::string_view filename); + + explicit IfdefGuardPrinter( + google::protobuf::io::Printer* p, absl::string_view filename, + absl::AnyInvocable<std::string(absl::string_view)> make_ifdef_identifier); + + ~IfdefGuardPrinter(); + + private: + google::protobuf::io::Printer* const p_; + const std::string ifdef_identifier_; +}; + +} // namespace cpp +} // namespace io +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__ diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc b/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc new file mode 100644 index 0000000000..49c73bb317 --- /dev/null +++ b/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc @@ -0,0 +1,92 @@ +#include "google/protobuf/io/cpp_utils/ifndef_guard.h" + +#include <string> + +#include <gtest/gtest.h> +#include "absl/log/absl_check.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "google/protobuf/io/printer.h" +#include "google/protobuf/io/zero_copy_stream.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +namespace google { +namespace protobuf { +namespace io { +namespace cpp { + +namespace { + +class IfnDefGuardTest : public testing::Test { + protected: + ZeroCopyOutputStream* output() { + ABSL_CHECK(stream_.has_value()); + return &*stream_; + } + absl::string_view written() { + stream_.reset(); + return out_; + } + + std::string out_; + absl::optional<StringOutputStream> stream_{&out_}; +}; + +TEST_F(IfnDefGuardTest, Basic) { + { + Printer printer(output(), '$'); + + const IfdefGuardPrinter ifdef_guard(&printer, "A/B/E/alpha"); + + EXPECT_FALSE(printer.failed()); + } + + EXPECT_EQ(written(), + "#ifndef A_B_E_ALPHA_\n" + "#define A_B_E_ALPHA_\n" + "\n" + "\n" + "#endif // A_B_E_ALPHA_\n"); +} + +TEST_F(IfnDefGuardTest, DifferentDelim) { + { + Printer printer(output(), '\0'); + + const IfdefGuardPrinter ifdef_guard(&printer, "A/B/E/alpha"); + + EXPECT_FALSE(printer.failed()); + } + + EXPECT_EQ(written(), + "#ifndef A_B_E_ALPHA_\n" + "#define A_B_E_ALPHA_\n" + "\n" + "\n" + "#endif // A_B_E_ALPHA_\n"); +} + +TEST_F(IfnDefGuardTest, DifferentSubstitutionFunction) { + { + Printer printer(output(), '$'); + + const IfdefGuardPrinter ifdef_guard( + &printer, "A/B/E/alpha", [](absl::string_view) { return "FOO_BAR_"; }); + + EXPECT_FALSE(printer.failed()); + } + + EXPECT_EQ(written(), + "#ifndef FOO_BAR_\n" + "#define FOO_BAR_\n" + "\n" + "\n" + "#endif // FOO_BAR_\n"); +} + +} // namespace +} // namespace cpp +} // namespace io + +} // namespace protobuf +} // namespace google