Skip to content

Commit

Permalink
Editions: Introduce functionality to protoc for generating edition fe…
Browse files Browse the repository at this point in the history
…ature set defaults.

This can be used by non-C++ runtimes and generators to seed feature resolution.  The output binary proto message will contain a mapping from edition to the default feature set, including whichever generator feature extensions are passed to protoc.

PiperOrigin-RevId: 563246376
  • Loading branch information
mkruskal-google authored and copybara-github committed Sep 6, 2023
1 parent c0b8696 commit 4019e25
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 1 deletion.
122 changes: 121 additions & 1 deletion src/google/protobuf/compiler/command_line_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "google/protobuf/compiler/allowlists/allowlists.h"
#include "google/protobuf/descriptor_legacy.h"
Expand Down Expand Up @@ -98,6 +99,7 @@
#include "google/protobuf/compiler/subprocess.h"
#include "google/protobuf/compiler/zip_writer.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/dynamic_message.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/io_win32.h"
Expand Down Expand Up @@ -1403,6 +1405,12 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
}
}

if (!experimental_edition_defaults_out_name_.empty()) {
if (!WriteExperimentalEditionDefaults(*descriptor_pool)) {
return 1;
}
}

if (mode_ == MODE_ENCODE || mode_ == MODE_DECODE) {
if (codec_type_.empty()) {
// HACK: Define an EmptyMessage type to use for decoding.
Expand Down Expand Up @@ -1684,6 +1692,9 @@ void CommandLineInterface::Clear() {
dependency_out_name_.clear();

experimental_editions_ = false;
experimental_edition_defaults_out_name_.clear();
experimental_edition_defaults_minimum_ = EDITION_UNKNOWN;
experimental_edition_defaults_maximum_ = EDITION_UNKNOWN;

mode_ = MODE_COMPILE;
print_mode_ = PRINT_NONE;
Expand Down Expand Up @@ -1915,7 +1926,8 @@ CommandLineInterface::ParseArgumentStatus CommandLineInterface::ParseArguments(
return PARSE_ARGUMENT_FAIL;
}
if (mode_ == MODE_COMPILE && output_directives_.empty() &&
descriptor_set_out_name_.empty()) {
descriptor_set_out_name_.empty() &&
experimental_edition_defaults_out_name_.empty()) {
std::cerr << "Missing output directives." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
Expand Down Expand Up @@ -2335,6 +2347,43 @@ CommandLineInterface::InterpretArgument(const std::string& name,
// experimental, undocumented, unsupported flag. Enable it at your own risk
// (or, just don't!).
experimental_editions_ = true;
} else if (name == "--experimental_edition_defaults_out") {
if (!experimental_edition_defaults_out_name_.empty()) {
std::cerr << name << " may only be passed once." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
if (value.empty()) {
std::cerr << name << " requires a non-empty value." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
if (mode_ != MODE_COMPILE) {
std::cerr
<< "Cannot use --encode or --decode and generate defaults at the "
"same time."
<< std::endl;
return PARSE_ARGUMENT_FAIL;
}
experimental_edition_defaults_out_name_ = value;
} else if (name == "--experimental_edition_defaults_minimum") {
if (experimental_edition_defaults_minimum_ != EDITION_UNKNOWN) {
std::cerr << name << " may only be passed once." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
if (!Edition_Parse(absl::StrCat("EDITION_", value),
&experimental_edition_defaults_minimum_)) {
std::cerr << name << " unknown edition \"" << value << "\"." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
} else if (name == "--experimental_edition_defaults_maximum") {
if (experimental_edition_defaults_maximum_ != EDITION_UNKNOWN) {
std::cerr << name << " may only be passed once." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
if (!Edition_Parse(absl::StrCat("EDITION_", value),
&experimental_edition_defaults_maximum_)) {
std::cerr << name << " unknown edition \"" << value << "\"." << std::endl;
return PARSE_ARGUMENT_FAIL;
}
} else {
// Some other flag. Look it up in the generators list.
const GeneratorInfo* generator_info = FindGeneratorByFlag(name);
Expand Down Expand Up @@ -2616,6 +2665,10 @@ bool CommandLineInterface::GenerateDependencyManifestFile(
output_filenames.push_back(descriptor_set_out_name_);
}

if (!experimental_edition_defaults_out_name_.empty()) {
output_filenames.push_back(experimental_edition_defaults_out_name_);
}

// Create the depfile, even if it will be empty.
int fd;
do {
Expand Down Expand Up @@ -2909,6 +2962,73 @@ bool CommandLineInterface::WriteDescriptorSet(
return true;
}

bool CommandLineInterface::WriteExperimentalEditionDefaults(
const DescriptorPool& pool) {
const Descriptor* feature_set =
pool.FindMessageTypeByName("google.protobuf.FeatureSet");
if (feature_set == nullptr) {
std::cerr << experimental_edition_defaults_out_name_
<< ": Could not find FeatureSet in descriptor pool. Please make "
"sure descriptor.proto is in your import path"
<< std::endl;
return false;
}
std::vector<const FieldDescriptor*> extensions;
pool.FindAllExtensions(feature_set, &extensions);

Edition minimum = PROTOBUF_MINIMUM_EDITION;
if (experimental_edition_defaults_minimum_ != EDITION_UNKNOWN) {
minimum = experimental_edition_defaults_minimum_;
}
Edition maximum = PROTOBUF_MAXIMUM_EDITION;
if (experimental_edition_defaults_maximum_ != EDITION_UNKNOWN) {
maximum = experimental_edition_defaults_maximum_;
}

absl::StatusOr<FeatureSetDefaults> defaults =
FeatureResolver::CompileDefaults(feature_set, extensions, minimum,
maximum);
if (!defaults.ok()) {
std::cerr << experimental_edition_defaults_out_name_ << ": "
<< defaults.status().message() << std::endl;
return false;
}

int fd;
do {
fd = open(experimental_edition_defaults_out_name_.c_str(),
O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
} while (fd < 0 && errno == EINTR);

if (fd < 0) {
perror(experimental_edition_defaults_out_name_.c_str());
return false;
}

io::FileOutputStream out(fd);

{
io::CodedOutputStream coded_out(&out);
// Determinism is useful here because build outputs are sometimes checked
// into version control.
coded_out.SetSerializationDeterministic(true);
if (!defaults->SerializeToCodedStream(&coded_out)) {
std::cerr << experimental_edition_defaults_out_name_ << ": "
<< strerror(out.GetErrno()) << std::endl;
out.Close();
return false;
}
}

if (!out.Close()) {
std::cerr << experimental_edition_defaults_out_name_ << ": "
<< strerror(out.GetErrno()) << std::endl;
return false;
}

return true;
}

const CommandLineInterface::GeneratorInfo*
CommandLineInterface::FindGeneratorByFlag(const std::string& name) const {
auto it = generators_by_flag_name_.find(name);
Expand Down
8 changes: 8 additions & 0 deletions src/google/protobuf/compiler/command_line_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/port.h"

// Must be included last.
Expand Down Expand Up @@ -311,6 +312,9 @@ class PROTOC_EXPORT CommandLineInterface {
bool WriteDescriptorSet(
const std::vector<const FileDescriptor*>& parsed_files);

// Implements the --experimental_edition_defaults_out option.
bool WriteExperimentalEditionDefaults(const DescriptorPool& pool);

// Implements the --dependency_out option
bool GenerateDependencyManifestFile(
const std::vector<const FileDescriptor*>& parsed_files,
Expand Down Expand Up @@ -448,6 +452,10 @@ class PROTOC_EXPORT CommandLineInterface {
// FileDescriptorSet should be written. Otherwise, empty.
std::string descriptor_set_out_name_;

std::string experimental_edition_defaults_out_name_;
Edition experimental_edition_defaults_minimum_;
Edition experimental_edition_defaults_maximum_;

// If --dependency_out was given, this is the path to the file where the
// dependency file will be written. Otherwise, empty.
std::string dependency_out_name_;
Expand Down
Loading

0 comments on commit 4019e25

Please sign in to comment.