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 }