These utilities provide a way to embed a FeatureSetDefaults message into generators or runtimes that need to implement feature resolution. They use protoc to handle the tricky reflection-based algorithm over feature protos, leaving only simple merges to be implemented in other languages. See docs/design/editions/editions-life-of-a-featureset.md for more information. PiperOrigin-RevId: 574554333pull/14430/head
parent
536066eb52
commit
898d8fa1fd
21 changed files with 503 additions and 55 deletions
@ -0,0 +1,36 @@ |
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 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
|
||||
|
||||
#include "google/protobuf/compiler/command_line_interface.h" |
||||
|
||||
#include "absl/log/initialize.h" |
||||
|
||||
// Must be included last.
|
||||
#include "google/protobuf/port_def.inc" |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
namespace compiler { |
||||
|
||||
// This is a version of protoc that has no built-in code generators.
|
||||
// See go/protobuf-toolchain-protoc
|
||||
int ProtocMain(int argc, char* argv[]) { |
||||
absl::InitializeLog(); |
||||
|
||||
CommandLineInterface cli; |
||||
cli.AllowPlugins("protoc-"); |
||||
|
||||
return cli.Run(argc, argv); |
||||
} |
||||
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
int main(int argc, char* argv[]) { |
||||
return google::protobuf::compiler::ProtocMain(argc, argv); |
||||
} |
@ -0,0 +1,112 @@ |
||||
# Protocol Buffers - Google's data interchange format |
||||
# Copyright 2023 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 |
||||
|
||||
""" |
||||
Provide a rule for generating the intermediate feature set defaults used for feature resolution. |
||||
|
||||
See go/life-of-a-featureset for more information. |
||||
""" |
||||
|
||||
def _compile_edition_defaults_impl(ctx): |
||||
out_file = ctx.actions.declare_file(ctx.outputs.output.basename) |
||||
sources = [] |
||||
paths = [] |
||||
for src in ctx.attr.srcs: |
||||
sources.extend(src[ProtoInfo].transitive_sources.to_list()) |
||||
paths.extend(src[ProtoInfo].transitive_proto_path.to_list()) |
||||
|
||||
args = ctx.actions.args() |
||||
args.add("--experimental_edition_defaults_out", out_file) |
||||
|
||||
args.add("--experimental_edition_defaults_minimum", ctx.attr.minimum_edition) |
||||
args.add("--experimental_edition_defaults_maximum", ctx.attr.maximum_edition) |
||||
for p in paths: |
||||
args.add("--proto_path", p) |
||||
for source in sources: |
||||
args.add(source) |
||||
ctx.actions.run( |
||||
outputs = [out_file], |
||||
inputs = sources, |
||||
executable = ctx.executable._protoc, |
||||
arguments = [args], |
||||
progress_message = "Generating edition defaults", |
||||
) |
||||
|
||||
compile_edition_defaults = rule( |
||||
attrs = { |
||||
"srcs": attr.label_list( |
||||
mandatory = True, |
||||
allow_rules = ["proto_library"], |
||||
providers = [ProtoInfo], |
||||
), |
||||
"minimum_edition": attr.string(mandatory = True), |
||||
"maximum_edition": attr.string(mandatory = True), |
||||
"_protoc": attr.label( |
||||
default = "//src/google/protobuf/compiler:protoc_minimal", |
||||
executable = True, |
||||
cfg = "exec", |
||||
), |
||||
}, |
||||
implementation = _compile_edition_defaults_impl, |
||||
outputs = { |
||||
"output": "%{name}.binpb", |
||||
}, |
||||
) |
||||
|
||||
def _embed_edition_defaults_impl(ctx): |
||||
ctx.actions.run_shell( |
||||
outputs = [ctx.outputs.output], |
||||
inputs = [ctx.file.defaults, ctx.file.template], |
||||
tools = [ctx.executable._escape], |
||||
command = """ |
||||
DEFAULTS_RAW=$({escape} < {defaults}) |
||||
# Windows requires extra escaping. |
||||
DEFAULTS_ESCAPED=$(echo $DEFAULTS_RAW | sed 's/\\\\/\\\\\\\\/g' || |
||||
echo $DEFAULTS_RAW | sed 's/\\\\\\\\/\\\\\\\\\\\\\\\\/g') |
||||
cp -f {template} {output} |
||||
# MacOS requires a backup file. |
||||
sed -i.bak \"s|{placeholder}|$DEFAULTS_ESCAPED|g\" {output} |
||||
""".format( |
||||
escape = ctx.executable._escape.path, |
||||
defaults = ctx.file.defaults.path, |
||||
template = ctx.file.template.path, |
||||
output = ctx.outputs.output.path, |
||||
placeholder = ctx.attr.placeholder, |
||||
), |
||||
) |
||||
|
||||
embed_edition_defaults = rule( |
||||
doc = "genrule to embed edition defaults binary data into a template file using octal C-style escaping.", |
||||
attrs = { |
||||
"defaults": attr.label( |
||||
mandatory = True, |
||||
allow_single_file = True, |
||||
allow_rules = ["compile_edition_defaults"], |
||||
providers = [ProtoInfo], |
||||
doc = "The compile_edition_defaults rule to embed", |
||||
), |
||||
"output": attr.output( |
||||
mandatory = True, |
||||
doc = "The name of the output file", |
||||
), |
||||
"template": attr.label( |
||||
mandatory = True, |
||||
allow_single_file = True, |
||||
doc = "The template to use for generating the output file", |
||||
), |
||||
"placeholder": attr.string( |
||||
mandatory = True, |
||||
doc = "The placeholder to replace with a serialized string in the template", |
||||
), |
||||
"_escape": attr.label( |
||||
default = "//src/google/protobuf/editions:internal_defaults_escape", |
||||
executable = True, |
||||
cfg = "exec", |
||||
), |
||||
}, |
||||
implementation = _embed_edition_defaults_impl, |
||||
) |
@ -0,0 +1,145 @@ |
||||
#include <string> |
||||
|
||||
#include "tools/cpp/runfiles/runfiles.h" |
||||
#include "google/protobuf/testing/file.h" |
||||
#include "google/protobuf/testing/file.h" |
||||
#include "google/protobuf/descriptor.pb.h" |
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
#include "absl/memory/memory.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/str_cat.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "google/protobuf/editions/defaults_test_embedded.h" |
||||
#include "google/protobuf/unittest_features.pb.h" |
||||
#include "google/protobuf/stubs/status_macros.h" |
||||
|
||||
// Must be included last.
|
||||
#include "google/protobuf/port_def.inc" |
||||
|
||||
#define ASSERT_OK(x) ASSERT_TRUE(x.ok()) << x.status().message(); |
||||
|
||||
namespace google { |
||||
namespace protobuf { |
||||
namespace { |
||||
|
||||
absl::StatusOr<FeatureSetDefaults> ReadDefaults(absl::string_view name) { |
||||
auto runfiles = absl::WrapUnique(bazel::tools::cpp::runfiles::Runfiles::CreateForTest()); |
||||
std::string file = runfiles->Rlocation(absl::StrCat( |
||||
"com_google_protobuf/src/google/protobuf/editions/", |
||||
name, ".binpb")); |
||||
std::string data; |
||||
RETURN_IF_ERROR(File::GetContents(file, &data, true)); |
||||
FeatureSetDefaults defaults; |
||||
if (!defaults.ParseFromString(data)) { |
||||
return absl::InternalError("Could not parse edition defaults!"); |
||||
} |
||||
return defaults; |
||||
} |
||||
|
||||
TEST(DefaultsTest, Check2023) { |
||||
auto defaults = ReadDefaults("test_defaults_2023"); |
||||
ASSERT_OK(defaults); |
||||
ASSERT_EQ(defaults->defaults().size(), 3); |
||||
ASSERT_EQ(defaults->minimum_edition(), EDITION_2023); |
||||
ASSERT_EQ(defaults->maximum_edition(), EDITION_2023); |
||||
|
||||
EXPECT_EQ(defaults->defaults()[0].edition(), EDITION_PROTO2); |
||||
EXPECT_EQ(defaults->defaults()[1].edition(), EDITION_PROTO3); |
||||
EXPECT_EQ(defaults->defaults()[2].edition(), EDITION_2023); |
||||
EXPECT_EQ(defaults->defaults()[2].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[2] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
1); |
||||
} |
||||
|
||||
TEST(DefaultsTest, CheckFuture) { |
||||
auto defaults = ReadDefaults("test_defaults_future"); |
||||
ASSERT_OK(defaults); |
||||
ASSERT_EQ(defaults->defaults().size(), 4); |
||||
ASSERT_EQ(defaults->minimum_edition(), EDITION_2023); |
||||
ASSERT_EQ(defaults->maximum_edition(), EDITION_99997_TEST_ONLY); |
||||
|
||||
EXPECT_EQ(defaults->defaults()[0].edition(), EDITION_PROTO2); |
||||
EXPECT_EQ(defaults->defaults()[1].edition(), EDITION_PROTO3); |
||||
EXPECT_EQ(defaults->defaults()[2].edition(), EDITION_2023); |
||||
EXPECT_EQ(defaults->defaults()[2].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[2] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
1); |
||||
EXPECT_EQ(defaults->defaults()[3].edition(), EDITION_99997_TEST_ONLY); |
||||
EXPECT_EQ(defaults->defaults()[3].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[3] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
2); |
||||
} |
||||
|
||||
TEST(DefaultsTest, CheckFarFuture) { |
||||
auto defaults = ReadDefaults("test_defaults_far_future"); |
||||
ASSERT_OK(defaults); |
||||
ASSERT_EQ(defaults->defaults().size(), 5); |
||||
ASSERT_EQ(defaults->minimum_edition(), EDITION_99997_TEST_ONLY); |
||||
ASSERT_EQ(defaults->maximum_edition(), EDITION_99999_TEST_ONLY); |
||||
|
||||
EXPECT_EQ(defaults->defaults()[0].edition(), EDITION_PROTO2); |
||||
EXPECT_EQ(defaults->defaults()[1].edition(), EDITION_PROTO3); |
||||
EXPECT_EQ(defaults->defaults()[2].edition(), EDITION_2023); |
||||
EXPECT_EQ(defaults->defaults()[2].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[2] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
1); |
||||
EXPECT_EQ(defaults->defaults()[3].edition(), EDITION_99997_TEST_ONLY); |
||||
EXPECT_EQ(defaults->defaults()[3].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[3] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
2); |
||||
EXPECT_EQ(defaults->defaults()[4].edition(), EDITION_99998_TEST_ONLY); |
||||
EXPECT_EQ(defaults->defaults()[4].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults->defaults()[4] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
3); |
||||
} |
||||
|
||||
TEST(DefaultsTest, Embedded) { |
||||
FeatureSetDefaults defaults; |
||||
ASSERT_TRUE(defaults.ParseFromArray(DEFAULTS_TEST_EMBEDDED, |
||||
sizeof(DEFAULTS_TEST_EMBEDDED) - 1)) |
||||
<< "Could not parse embedded data"; |
||||
ASSERT_EQ(defaults.defaults().size(), 3); |
||||
ASSERT_EQ(defaults.minimum_edition(), EDITION_2023); |
||||
ASSERT_EQ(defaults.maximum_edition(), EDITION_2023); |
||||
|
||||
EXPECT_EQ(defaults.defaults()[0].edition(), EDITION_PROTO2); |
||||
EXPECT_EQ(defaults.defaults()[1].edition(), EDITION_PROTO3); |
||||
EXPECT_EQ(defaults.defaults()[2].edition(), EDITION_2023); |
||||
EXPECT_EQ(defaults.defaults()[2].features().field_presence(), |
||||
FeatureSet::EXPLICIT); |
||||
EXPECT_EQ(defaults.defaults()[2] |
||||
.features() |
||||
.GetExtension(pb::test) |
||||
.int_file_feature(), |
||||
1); |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
@ -0,0 +1,35 @@ |
||||
#include <iostream> |
||||
#include <string> |
||||
|
||||
#ifdef _WIN32 |
||||
#include <fcntl.h> |
||||
#else |
||||
#include <unistd.h> |
||||
#endif |
||||
|
||||
#include "google/protobuf/descriptor.pb.h" |
||||
#include "absl/strings/escaping.h" |
||||
|
||||
#if defined(_WIN32) |
||||
#include "google/protobuf/io/io_win32.h" |
||||
|
||||
// DO NOT include <io.h>, instead create functions in io_win32.{h,cc} and import
|
||||
// them like we do below.
|
||||
using google::protobuf::io::win32::setmode; |
||||
#endif |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
#ifdef _WIN32 |
||||
setmode(STDIN_FILENO, _O_BINARY); |
||||
setmode(STDOUT_FILENO, _O_BINARY); |
||||
#endif |
||||
google::protobuf::FeatureSetDefaults defaults; |
||||
if (!defaults.ParseFromFileDescriptor(STDIN_FILENO)) { |
||||
std::cerr << argv[0] << ": unable to parse edition defaults." << std::endl; |
||||
return 1; |
||||
} |
||||
std::string output; |
||||
defaults.SerializeToString(&output); |
||||
std::cout << absl::CEscape(output); |
||||
return 0; |
||||
} |
Loading…
Reference in new issue