kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/cxx/tools/proto_metadata_plugin.cc (about)

     1  /*
     2   * Copyright 2019 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  #include <cstdlib>
    17  #include <string>
    18  
    19  #include "absl/container/flat_hash_map.h"
    20  #include "absl/container/node_hash_map.h"
    21  #include "absl/log/log.h"
    22  #include "absl/memory/memory.h"
    23  #include "absl/strings/escaping.h"
    24  #include "absl/strings/match.h"
    25  #include "absl/strings/str_join.h"
    26  #include "absl/strings/str_split.h"
    27  #include "absl/strings/string_view.h"
    28  #include "absl/strings/strip.h"
    29  #include "google/protobuf/compiler/code_generator.h"
    30  #include "google/protobuf/compiler/cpp/generator.h"
    31  #include "google/protobuf/compiler/plugin.h"
    32  #include "google/protobuf/compiler/plugin.pb.h"
    33  #include "google/protobuf/io/gzip_stream.h"
    34  #include "google/protobuf/io/printer.h"
    35  #include "google/protobuf/io/zero_copy_stream_impl.h"
    36  #include "kythe/cxx/common/init.h"
    37  
    38  namespace kythe {
    39  namespace {
    40  using ::google::protobuf::FileDescriptor;
    41  using ::google::protobuf::compiler::GeneratorContext;
    42  using ::google::protobuf::compiler::cpp::CppGenerator;
    43  using ::google::protobuf::io::GzipOutputStream;
    44  using ::google::protobuf::io::Printer;
    45  using ::google::protobuf::io::StringOutputStream;
    46  using ::google::protobuf::io::ZeroCopyOutputStream;
    47  
    48  constexpr absl::string_view kMetadataFileSuffix = ".meta";
    49  constexpr absl::string_view kHeaderFileSuffix = ".h";
    50  constexpr absl::string_view kCompressMetadataParam = "compress_metadata";
    51  constexpr absl::string_view kAnnotateHeaderParam = "annotate_headers";
    52  constexpr absl::string_view kAnnotationGuardParam = "annotation_guard_name";
    53  constexpr absl::string_view kAnnotationGuardDefault = "KYTHE_IS_RUNNING";
    54  constexpr absl::string_view kAnnotationPragmaParam = "annotation_pragma_name";
    55  constexpr absl::string_view kAnnotationPragmaInline = "kythe_inline_metadata";
    56  constexpr absl::string_view kAnnotationPragmaCompress =
    57      "kythe_inline_gzip_metadata";
    58  constexpr int kMetadataLineLength = 76;
    59  
    60  absl::string_view NextChunk(absl::string_view* data, int size) {
    61    if (data->empty()) {
    62      return {};
    63    }
    64    absl::string_view result = data->substr(0, size);
    65    data->remove_prefix(result.size());
    66    return result;
    67  }
    68  
    69  void WriteLines(absl::string_view metadata, Printer* printer) {
    70    while (!metadata.empty()) {
    71      absl::string_view chunk = NextChunk(&metadata, kMetadataLineLength);
    72      printer->WriteRaw(chunk.data(), chunk.size());
    73      printer->PrintRaw("\n");
    74    }
    75  }
    76  
    77  std::string WriteMetadata(absl::string_view metafile,
    78                            absl::string_view contents,
    79                            ZeroCopyOutputStream* stream) {
    80    if (!absl::EndsWith(metafile, kMetadataFileSuffix)) {
    81      return "Not a metadata file";
    82    }
    83  
    84    Printer printer(stream, '\n');
    85    printer.PrintRaw("/* ");
    86    printer.WriteRaw(metafile.data(), metafile.size());
    87    printer.PrintRaw("\n");
    88    std::string metadata;
    89    absl::Base64Escape(contents, &metadata);
    90    WriteLines(metadata, &printer);
    91    printer.PrintRaw("*/\n");
    92  
    93    return "";
    94  }
    95  
    96  // Trivial ZeroCopyOutputStream wrapper which allows ownership to be retained
    97  // by the context so that the metadata can be appended to the end.
    98  class WrappedOutputStream : public ZeroCopyOutputStream {
    99   public:
   100    explicit WrappedOutputStream(ZeroCopyOutputStream* wrapped)
   101        : wrapped_(wrapped) {}
   102    bool Next(void** data, int* size) override {
   103      return wrapped_->Next(data, size);
   104    }
   105    void BackUp(int count) override { return wrapped_->BackUp(count); }
   106    int64_t ByteCount() const override { return wrapped_->ByteCount(); }
   107    bool WriteAliasedRaw(const void* data, int size) override {
   108      return wrapped_->WriteAliasedRaw(data, size);
   109    }
   110    bool AllowsAliasing() const override { return wrapped_->AllowsAliasing(); }
   111  
   112   private:
   113    ZeroCopyOutputStream* wrapped_;
   114  };
   115  
   116  class GzipStringOutputStream : public ZeroCopyOutputStream {
   117   public:
   118    explicit GzipStringOutputStream(std::string* output)
   119        : string_stream_(output) {}
   120  
   121    bool Next(void** data, int* size) override {
   122      return gzip_stream_.Next(data, size);
   123    }
   124    void BackUp(int count) override { return gzip_stream_.BackUp(count); }
   125    int64_t ByteCount() const override { return gzip_stream_.ByteCount(); }
   126    bool WriteAliasedRaw(const void* data, int size) override {
   127      return gzip_stream_.WriteAliasedRaw(data, size);
   128    }
   129    bool AllowsAliasing() const override { return gzip_stream_.AllowsAliasing(); }
   130  
   131   private:
   132    StringOutputStream string_stream_;
   133    GzipOutputStream gzip_stream_{&string_stream_};
   134  };
   135  
   136  class WrappedContext : public GeneratorContext {
   137   public:
   138    explicit WrappedContext(GeneratorContext* wrapped, bool compress_metadata)
   139        : compress_metadata_(compress_metadata), wrapped_(wrapped) {}
   140  
   141    // Open the file for writing.
   142    ZeroCopyOutputStream* Open(const std::string& filename) override {
   143      // If it's a metadata file, preserve the contents in a string.
   144      if (absl::EndsWith(filename, kMetadataFileSuffix)) {
   145        if (compress_metadata_) {
   146          return new GzipStringOutputStream(&metadata_map_[filename]);
   147        } else {
   148          return new StringOutputStream(&metadata_map_[filename]);
   149        }
   150      }
   151      // If it's a header file, retain ownership of the stream so that we can
   152      // append to it later (OpenForAppend does not work be default).
   153      if (absl::EndsWith(filename, kHeaderFileSuffix)) {
   154        return new WrappedOutputStream(
   155            (stream_map_[filename] = absl::WrapUnique(wrapped_->Open(filename)))
   156                .get());
   157      }
   158  
   159      // Else, it's a source file.  Just pass it through.
   160      return wrapped_->Open(filename);
   161    }
   162  
   163    // Write the retained metadata in a comment at the end of the file.
   164    bool WriteEmbeddedMetadata(std::string* error) {
   165      for (const auto& entry : metadata_map_) {
   166        std::string header(absl::StripSuffix(entry.first, kMetadataFileSuffix));
   167        auto stream_iter = stream_map_.find(header);
   168        if (stream_iter != stream_map_.end()) {
   169          std::string err =
   170              WriteMetadata(entry.first, entry.second, stream_iter->second.get());
   171          if (!err.empty()) {
   172            *error = err;
   173            return false;
   174          }
   175        }
   176      }
   177      return true;
   178    }
   179  
   180    ZeroCopyOutputStream* OpenForAppend(const std::string&) override {
   181      return nullptr;
   182    }
   183  
   184    ZeroCopyOutputStream* OpenForInsert(const std::string&,
   185                                        const std::string&) override {
   186      return nullptr;
   187    }
   188  
   189    void ListParsedFiles(std::vector<const FileDescriptor*>* output) override {
   190      wrapped_->ListParsedFiles(output);
   191    }
   192  
   193    void GetCompilerVersion(
   194        google::protobuf::compiler::Version* version) const override {
   195      wrapped_->GetCompilerVersion(version);
   196    }
   197  
   198   private:
   199    bool compress_metadata_;
   200    GeneratorContext* wrapped_;
   201    // Map from header name -> metadata contents.
   202    absl::node_hash_map<std::string, std::string> metadata_map_;
   203    absl::flat_hash_map<std::string, std::unique_ptr<ZeroCopyOutputStream>>
   204        stream_map_;
   205  };
   206  
   207  // Returns true if the compress_metadata parameter is present.
   208  // Adjusts the parameter argument to remove compress_metadata and include
   209  // requisite options for metadata generation.
   210  bool CompressMetadata(std::string* parameter) {
   211    bool has_guard_name = false;
   212    bool compress_metadata = false;
   213    std::vector<absl::string_view> parts = {kAnnotateHeaderParam};
   214    for (absl::string_view param : absl::StrSplit(*parameter, ',')) {
   215      if (absl::StartsWith(param, kAnnotationPragmaParam) ||
   216          param == kAnnotateHeaderParam) {
   217        continue;
   218      }
   219      if (param == kCompressMetadataParam) {
   220        compress_metadata = true;
   221        continue;
   222      }
   223      if (absl::StartsWith(param, kAnnotationGuardParam)) {
   224        has_guard_name = true;
   225      }
   226      parts.push_back(param);
   227    }
   228    *parameter = absl::StrJoin(parts, ",");
   229    absl::StrAppend(
   230        parameter, ",", kAnnotationPragmaParam, "=",
   231        compress_metadata ? kAnnotationPragmaCompress : kAnnotationPragmaInline);
   232    if (!has_guard_name) {
   233      absl::StrAppend(parameter, ",", kAnnotationGuardParam, "=",
   234                      kAnnotationGuardDefault);
   235    }
   236    return compress_metadata;
   237  }
   238  
   239  class EmbeddedMetadataGenerator : public CppGenerator {
   240    bool Generate(const FileDescriptor* file, const std::string& parameter,
   241                  GeneratorContext* context, std::string* error) const override {
   242      std::string cpp_parameter = parameter;
   243      WrappedContext wrapper(context, CompressMetadata(&cpp_parameter));
   244      if (CppGenerator::Generate(file, cpp_parameter, &wrapper, error)) {
   245        return wrapper.WriteEmbeddedMetadata(error);
   246      }
   247      return false;
   248    }
   249  };
   250  
   251  }  // namespace
   252  }  // namespace kythe
   253  
   254  int main(int argc, char** argv) {
   255    kythe::InitializeProgram(argv[0]);
   256    kythe::EmbeddedMetadataGenerator generator;
   257    return google::protobuf::compiler::PluginMain(argc, argv, &generator);
   258  }