1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <errno.h> 6#include <stdio.h> 7#include <string.h> 8#include <sys/stat.h> 9 10#include <fstream> 11#include <iostream> 12#include <map> 13#include <memory> 14#include <string> 15#include <utility> 16#include <vector> 17 18#include <fidl/c_generator.h> 19#include <fidl/flat_ast.h> 20#include <fidl/identifier_table.h> 21#include <fidl/json_generator.h> 22#include <fidl/lexer.h> 23#include <fidl/library_zx.h> 24#include <fidl/names.h> 25#include <fidl/parser.h> 26#include <fidl/source_manager.h> 27#include <fidl/tables_generator.h> 28 29namespace { 30 31void Usage() { 32 std::cout 33 << "usage: fidlc [--c-header HEADER_PATH]\n" 34 " [--c-client CLIENT_PATH]\n" 35 " [--c-server SERVER_PATH]\n" 36 " [--tables TABLES_PATH]\n" 37 " [--json JSON_PATH]\n" 38 " [--name LIBRARY_NAME]\n" 39 " [--files [FIDL_FILE...]...]\n" 40 " [--help]\n" 41 "\n" 42 " * `--c-header HEADER_PATH`. If present, this flag instructs `fidlc` to output\n" 43 " a C header at the given path.\n" 44 "\n" 45 " * `--c-client CLIENT_PATH`. If present, this flag instructs `fidlc` to output\n" 46 " the simple C client implementation at the given path.\n" 47 "\n" 48 " * `--c-server SERVER_PATH`. If present, this flag instructs `fidlc` to output\n" 49 " the simple C server implementation at the given path.\n" 50 "\n" 51 " * `--tables TABLES_PATH`. If present, this flag instructs `fidlc` to output\n" 52 " coding tables at the given path. The coding tables are required to encode and\n" 53 " decode messages from the C and C++ bindings.\n" 54 "\n" 55 " * `--json JSON_PATH`. If present, this flag instructs `fidlc` to output the\n" 56 " library's intermediate representation at the given path. The intermediate\n" 57 " representation is JSON that conforms to a particular schema (located at\n" 58 " https://fuchsia.googlesource.com/zircon/+/master/system/host/fidl/schema.json).\n" 59 " The intermediate representation is used as input to the various backends.\n" 60 "\n" 61 " * `--name LIBRARY_NAME`. If present, this flag instructs `fidlc` to validate\n" 62 " that the library being compiled has the given name. This flag is useful to\n" 63 " cross-check between the library's declaration in a build system and the\n" 64 " actual contents of the library.\n" 65 "\n" 66 " * `--files [FIDL_FILE...]...`. Each `--file [FIDL_FILE...]` chunk of arguments\n" 67 " describes a library, all of which must share the same top-level library name\n" 68 " declaration. Libraries must be presented in dependency order, with later\n" 69 " libraries able to use declarations from preceding libraries but not vice versa.\n" 70 " Output is only generated for the final library, not for each of its dependencies.\n" 71 "\n" 72 " * `--help`. Prints this help, and exit immediately.\n" 73 "\n" 74 "All of the arguments can also be provided via a response file, denoted as\n" 75 "`@responsefile`. The contents of the file at `responsefile` will be interpreted\n" 76 "as a whitespace-delimited list of arguments. Response files cannot be nested,\n" 77 "and must be the only argument.\n" 78 "\n" 79 "See <https://fuchsia.googlesource.com/zircon/+/master/docs/fidl/compiler.md>\n" 80 "for more information.\n"; 81 std::cout.flush(); 82} 83 84[[noreturn]] void FailWithUsage(const char* message, ...) { 85 va_list args; 86 va_start(args, message); 87 vfprintf(stderr, message, args); 88 va_end(args); 89 Usage(); 90 exit(1); 91} 92 93[[noreturn]] void Fail(const char* message, ...) { 94 va_list args; 95 va_start(args, message); 96 vfprintf(stderr, message, args); 97 va_end(args); 98 exit(1); 99} 100 101void MakeParentDirectory(const std::string& filename) { 102 std::string::size_type slash = 0; 103 104 for (;;) { 105 slash = filename.find('/', slash); 106 if (slash == filename.npos) { 107 return; 108 } 109 110 std::string path = filename.substr(0, slash); 111 ++slash; 112 if (path.size() == 0u) { 113 // Skip creating "/". 114 continue; 115 } 116 117 if (mkdir(path.data(), 0755) != 0 && errno != EEXIST) { 118 Fail("Could not create directory %s for output file %s: error %s\n", 119 path.data(), filename.data(), strerror(errno)); 120 } 121 } 122} 123 124std::fstream Open(std::string filename, std::ios::openmode mode) { 125 if ((mode & std::ios::out) != 0) { 126 MakeParentDirectory(filename); 127 } 128 129 std::fstream stream; 130 stream.open(filename, mode); 131 if (!stream.is_open()) { 132 Fail("Could not open file: %s\n", filename.data()); 133 } 134 return stream; 135} 136 137class Arguments { 138public: 139 virtual ~Arguments() {} 140 141 virtual std::string Claim() = 0; 142 virtual bool Remaining() const = 0; 143}; 144 145class ArgvArguments : public Arguments { 146public: 147 ArgvArguments(int count, char** arguments) 148 : count_(count), arguments_(const_cast<const char**>(arguments)) {} 149 150 std::string Claim() override { 151 if (count_ < 1) { 152 FailWithUsage("Missing part of an argument\n"); 153 } 154 std::string argument = arguments_[0]; 155 --count_; 156 ++arguments_; 157 return argument; 158 } 159 160 bool Remaining() const override { return count_ > 0; } 161 162 bool HeadIsResponseFile() { 163 if (count_ == 0) { 164 return false; 165 } 166 return arguments_[0][0] == '@'; 167 } 168 169private: 170 int count_; 171 const char** arguments_; 172}; 173 174class ResponseFileArguments : public Arguments { 175public: 176 ResponseFileArguments(fidl::StringView filename) 177 : file_(Open(filename, std::ios::in)) { 178 ConsumeWhitespace(); 179 } 180 181 std::string Claim() override { 182 std::string argument; 183 while (Remaining() && !IsWhitespace()) { 184 argument.push_back(file_.get()); 185 } 186 ConsumeWhitespace(); 187 return argument; 188 } 189 190 bool Remaining() const override { return !file_.eof(); } 191 192private: 193 bool IsWhitespace() { 194 switch (file_.peek()) { 195 case ' ': 196 case '\n': 197 case '\r': 198 case '\t': 199 return true; 200 default: 201 return false; 202 } 203 } 204 205 void ConsumeWhitespace() { 206 while (Remaining() && IsWhitespace()) { 207 file_.get(); 208 } 209 } 210 211 std::fstream file_; 212}; 213 214enum struct Behavior { 215 kCHeader, 216 kCClient, 217 kCServer, 218 kTables, 219 kJSON, 220}; 221 222bool Parse(const fidl::SourceFile& source_file, fidl::IdentifierTable* identifier_table, 223 fidl::ErrorReporter* error_reporter, fidl::flat::Library* library) { 224 fidl::Lexer lexer(source_file, identifier_table); 225 fidl::Parser parser(&lexer, error_reporter); 226 auto ast = parser.Parse(); 227 if (!parser.Ok()) { 228 return false; 229 } 230 if (!library->ConsumeFile(std::move(ast))) { 231 return false; 232 } 233 return true; 234} 235 236void Write(std::ostringstream output, std::fstream file) { 237 file << output.str(); 238 file.flush(); 239} 240 241} // namespace 242 243int main(int argc, char* argv[]) { 244 auto argv_args = std::make_unique<ArgvArguments>(argc, argv); 245 246 // Parse the program name. 247 argv_args->Claim(); 248 249 if (!argv_args->Remaining()) { 250 Usage(); 251 exit(0); 252 } 253 254 // Check for a response file. After this, |args| is either argv or 255 // the response file contents. 256 Arguments* args = argv_args.get(); 257 std::unique_ptr<ResponseFileArguments> response_file_args; 258 if (argv_args->HeadIsResponseFile()) { 259 std::string response = args->Claim(); 260 if (argv_args->Remaining()) { 261 // Response file must be the only argument. 262 FailWithUsage("Response files must be the only argument to %s.\n", argv[0]); 263 } 264 // Drop the leading '@'. 265 fidl::StringView response_file = response.data() + 1; 266 response_file_args = std::make_unique<ResponseFileArguments>(response_file); 267 args = response_file_args.get(); 268 } 269 270 std::string library_name; 271 272 std::map<Behavior, std::fstream> outputs; 273 while (args->Remaining()) { 274 // Try to parse an output type. 275 std::string behavior_argument = args->Claim(); 276 std::fstream output_file; 277 if (behavior_argument == "--help") { 278 Usage(); 279 exit(0); 280 } else if (behavior_argument == "--c-header") { 281 outputs.emplace(Behavior::kCHeader, Open(args->Claim(), std::ios::out)); 282 } else if (behavior_argument == "--c-client") { 283 outputs.emplace(Behavior::kCClient, Open(args->Claim(), std::ios::out)); 284 } else if (behavior_argument == "--c-server") { 285 outputs.emplace(Behavior::kCServer, Open(args->Claim(), std::ios::out)); 286 } else if (behavior_argument == "--tables") { 287 outputs.emplace(Behavior::kTables, Open(args->Claim(), std::ios::out)); 288 } else if (behavior_argument == "--json") { 289 outputs.emplace(Behavior::kJSON, Open(args->Claim(), std::ios::out)); 290 } else if (behavior_argument == "--name") { 291 library_name = args->Claim(); 292 } else if (behavior_argument == "--files") { 293 // Start parsing filenames. 294 break; 295 } else { 296 FailWithUsage("Unknown argument: %s\n", behavior_argument.data()); 297 } 298 } 299 300 // Parse libraries. 301 std::vector<fidl::SourceManager> source_managers; 302 source_managers.push_back(fidl::SourceManager()); 303 std::string library_zx_data(fidl::LibraryZX::kData, strlen(fidl::LibraryZX::kData) + 1); 304 source_managers.back().AddSourceFile( 305 std::make_unique<fidl::SourceFile>(fidl::LibraryZX::kFilename, std::move(library_zx_data))); 306 source_managers.push_back(fidl::SourceManager()); 307 while (args->Remaining()) { 308 std::string arg = args->Claim(); 309 if (arg == "--files") { 310 source_managers.emplace_back(); 311 } else { 312 if (!source_managers.back().CreateSource(arg.data())) { 313 Fail("Couldn't read in source data from %s\n", arg.data()); 314 } 315 } 316 } 317 318 fidl::IdentifierTable identifier_table; 319 fidl::ErrorReporter error_reporter; 320 fidl::flat::Libraries all_libraries; 321 const fidl::flat::Library* final_library = nullptr; 322 for (const auto& source_manager : source_managers) { 323 if (source_manager.sources().empty()) { 324 continue; 325 } 326 auto library = std::make_unique<fidl::flat::Library>(&all_libraries, &error_reporter); 327 for (const auto& source_file : source_manager.sources()) { 328 if (!Parse(*source_file, &identifier_table, &error_reporter, library.get())) { 329 error_reporter.PrintReports(); 330 return 1; 331 } 332 } 333 if (!library->Compile()) { 334 error_reporter.PrintReports(); 335 return 1; 336 } 337 final_library = library.get(); 338 if (!all_libraries.Insert(std::move(library))) { 339 const auto& name = library->name(); 340 Fail("Mulitple libraries with the same name: '%s'\n", 341 NameLibrary(name).data()); 342 } 343 } 344 if (final_library == nullptr) { 345 Fail("No library was produced.\n"); 346 } 347 348 // Verify that the produced library's name matches the expected name. 349 std::string final_name = NameLibrary(final_library->name()); 350 if (!library_name.empty() && final_name != library_name) { 351 Fail("Generated library '%s' did not match --name argument: %s\n", 352 final_name.data(), library_name.data()); 353 } 354 355 // We recompile dependencies, and only emit output for the final 356 // library. 357 for (auto& output : outputs) { 358 auto& behavior = output.first; 359 auto& output_file = output.second; 360 361 switch (behavior) { 362 case Behavior::kCHeader: { 363 fidl::CGenerator generator(final_library); 364 Write(generator.ProduceHeader(), std::move(output_file)); 365 break; 366 } 367 case Behavior::kCClient: { 368 fidl::CGenerator generator(final_library); 369 Write(generator.ProduceClient(), std::move(output_file)); 370 break; 371 } 372 case Behavior::kCServer: { 373 fidl::CGenerator generator(final_library); 374 Write(generator.ProduceServer(), std::move(output_file)); 375 break; 376 } 377 case Behavior::kTables: { 378 fidl::TablesGenerator generator(final_library); 379 Write(generator.Produce(), std::move(output_file)); 380 break; 381 } 382 case Behavior::kJSON: { 383 fidl::JSONGenerator generator(final_library); 384 Write(generator.Produce(), std::move(output_file)); 385 break; 386 } 387 } 388 } 389} 390