| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include <fstream> |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <fidl/c_generator.h> |
| #include <fidl/flat_ast.h> |
| #include <fidl/json_generator.h> |
| #include <fidl/json_schema.h> |
| #include <fidl/lexer.h> |
| #include <fidl/library_zx.h> |
| #include <fidl/names.h> |
| #include <fidl/parser.h> |
| #include <fidl/source_manager.h> |
| #include <fidl/tables_generator.h> |
| |
| namespace { |
| |
| void Usage() { |
| std::cout |
| << "usage: fidlc [--c-header HEADER_PATH]\n" |
| " [--c-client CLIENT_PATH]\n" |
| " [--c-server SERVER_PATH]\n" |
| " [--tables TABLES_PATH]\n" |
| " [--json JSON_PATH]\n" |
| " [--name LIBRARY_NAME]\n" |
| " [--werror]\n" |
| " [--files [FIDL_FILE...]...]\n" |
| " [--help]\n" |
| "\n" |
| " * `--c-header HEADER_PATH`. If present, this flag instructs `fidlc` to output\n" |
| " a C header at the given path.\n" |
| "\n" |
| " * `--c-client CLIENT_PATH`. If present, this flag instructs `fidlc` to output\n" |
| " the simple C client implementation at the given path.\n" |
| "\n" |
| " * `--c-server SERVER_PATH`. If present, this flag instructs `fidlc` to output\n" |
| " the simple C server implementation at the given path.\n" |
| "\n" |
| " * `--tables TABLES_PATH`. If present, this flag instructs `fidlc` to output\n" |
| " coding tables at the given path. The coding tables are required to encode and\n" |
| " decode messages from the C and C++ bindings.\n" |
| "\n" |
| " * `--json JSON_PATH`. If present, this flag instructs `fidlc` to output the\n" |
| " library's intermediate representation at the given path. The intermediate\n" |
| " representation is JSON that conforms to the schema available via --json-schema.\n" |
| " The intermediate representation is used as input to the various backends.\n" |
| "\n" |
| " * `--name LIBRARY_NAME`. If present, this flag instructs `fidlc` to validate\n" |
| " that the library being compiled has the given name. This flag is useful to\n" |
| " cross-check between the library's declaration in a build system and the\n" |
| " actual contents of the library.\n" |
| "\n" |
| " * `--files [FIDL_FILE...]...`. Each `--file [FIDL_FILE...]` chunk of arguments\n" |
| " describes a library, all of which must share the same top-level library name\n" |
| " declaration. Libraries must be presented in dependency order, with later\n" |
| " libraries able to use declarations from preceding libraries but not vice versa.\n" |
| " Output is only generated for the final library, not for each of its dependencies.\n" |
| "\n" |
| " * `--json-schema`. If present, this flag instructs `fidlc` to output the\n" |
| " JSON schema of the intermediate representation.\n" |
| "\n" |
| " * `--werror`. Treats warnings as errors.\n" |
| "\n" |
| " * `--help`. Prints this help, and exit immediately.\n" |
| "\n" |
| "All of the arguments can also be provided via a response file, denoted as\n" |
| "`@responsefile`. The contents of the file at `responsefile` will be interpreted\n" |
| "as a whitespace-delimited list of arguments. Response files cannot be nested,\n" |
| "and must be the only argument.\n" |
| "\n" |
| "See <https://fuchsia.googlesource.com/fuchsia/+/master/zircon/docs/fidl/compiler.md>\n" |
| "for more information.\n"; |
| std::cout.flush(); |
| } |
| |
| void PrintJsonSchema() { |
| std::cout << JsonSchema::schema() << "\n"; |
| std::cout.flush(); |
| } |
| |
| [[noreturn]] void FailWithUsage(const char* message, ...) { |
| va_list args; |
| va_start(args, message); |
| vfprintf(stderr, message, args); |
| va_end(args); |
| Usage(); |
| exit(1); |
| } |
| |
| [[noreturn]] void Fail(const char* message, ...) { |
| va_list args; |
| va_start(args, message); |
| vfprintf(stderr, message, args); |
| va_end(args); |
| exit(1); |
| } |
| |
| void MakeParentDirectory(const std::string& filename) { |
| std::string::size_type slash = 0; |
| |
| for (;;) { |
| slash = filename.find('/', slash); |
| if (slash == filename.npos) { |
| return; |
| } |
| |
| std::string path = filename.substr(0, slash); |
| ++slash; |
| if (path.size() == 0u) { |
| // Skip creating "/". |
| continue; |
| } |
| |
| if (mkdir(path.data(), 0755) != 0 && errno != EEXIST) { |
| Fail("Could not create directory %s for output file %s: error %s\n", |
| path.data(), filename.data(), strerror(errno)); |
| } |
| } |
| } |
| |
| std::fstream Open(std::string filename, std::ios::openmode mode) { |
| if ((mode & std::ios::out) != 0) { |
| MakeParentDirectory(filename); |
| } |
| |
| std::fstream stream; |
| stream.open(filename, mode); |
| if (!stream.is_open()) { |
| Fail("Could not open file: %s\n", filename.data()); |
| } |
| return stream; |
| } |
| |
| class Arguments { |
| public: |
| virtual ~Arguments() {} |
| |
| virtual std::string Claim() = 0; |
| virtual bool Remaining() const = 0; |
| }; |
| |
| class ArgvArguments : public Arguments { |
| public: |
| ArgvArguments(int count, char** arguments) |
| : count_(count), arguments_(const_cast<const char**>(arguments)) {} |
| |
| std::string Claim() override { |
| if (count_ < 1) { |
| FailWithUsage("Missing part of an argument\n"); |
| } |
| std::string argument = arguments_[0]; |
| --count_; |
| ++arguments_; |
| return argument; |
| } |
| |
| bool Remaining() const override { return count_ > 0; } |
| |
| bool HeadIsResponseFile() { |
| if (count_ == 0) { |
| return false; |
| } |
| return arguments_[0][0] == '@'; |
| } |
| |
| private: |
| int count_; |
| const char** arguments_; |
| }; |
| |
| class ResponseFileArguments : public Arguments { |
| public: |
| ResponseFileArguments(std::string_view filename) |
| : file_(Open(std::string(filename), std::ios::in)) { |
| ConsumeWhitespace(); |
| } |
| |
| std::string Claim() override { |
| std::string argument; |
| while (Remaining() && !IsWhitespace()) { |
| argument.push_back(static_cast<char>(file_.get())); |
| } |
| ConsumeWhitespace(); |
| return argument; |
| } |
| |
| bool Remaining() const override { return !file_.eof(); } |
| |
| private: |
| bool IsWhitespace() { |
| switch (file_.peek()) { |
| case ' ': |
| case '\n': |
| case '\r': |
| case '\t': |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void ConsumeWhitespace() { |
| while (Remaining() && IsWhitespace()) { |
| file_.get(); |
| } |
| } |
| |
| std::fstream file_; |
| }; |
| |
| enum struct Behavior { |
| kCHeader, |
| kCClient, |
| kCServer, |
| kTables, |
| kJSON, |
| }; |
| |
| bool Parse(const fidl::SourceFile& source_file, |
| fidl::ErrorReporter* error_reporter, fidl::flat::Library* library) { |
| fidl::Lexer lexer(source_file, error_reporter); |
| fidl::Parser parser(&lexer, error_reporter); |
| auto ast = parser.Parse(); |
| if (!parser.Ok()) { |
| return false; |
| } |
| if (!library->ConsumeFile(std::move(ast))) { |
| return false; |
| } |
| return true; |
| } |
| |
| void Write(std::ostringstream output, std::fstream file) { |
| file << output.str(); |
| file.flush(); |
| } |
| |
| } // namespace |
| |
| // TODO(pascallouis): remove forward declaration, this was only introduced to |
| // reduce diff size while breaking things up. |
| int compile(fidl::ErrorReporter* error_reporter, |
| fidl::flat::Typespace* typespace, |
| std::string library_name, |
| std::map<std::pair<Behavior, std::string>, std::fstream> outputs, |
| std::vector<fidl::SourceManager> source_managers); |
| |
| int main(int argc, char* argv[]) { |
| auto argv_args = std::make_unique<ArgvArguments>(argc, argv); |
| |
| // Parse the program name. |
| argv_args->Claim(); |
| |
| if (!argv_args->Remaining()) { |
| Usage(); |
| exit(0); |
| } |
| |
| // Check for a response file. After this, |args| is either argv or |
| // the response file contents. |
| Arguments* args = argv_args.get(); |
| std::unique_ptr<ResponseFileArguments> response_file_args; |
| if (argv_args->HeadIsResponseFile()) { |
| std::string response = args->Claim(); |
| if (argv_args->Remaining()) { |
| // Response file must be the only argument. |
| FailWithUsage("Response files must be the only argument to %s.\n", argv[0]); |
| } |
| // Drop the leading '@'. |
| std::string_view response_file = response.data() + 1; |
| response_file_args = std::make_unique<ResponseFileArguments>(response_file); |
| args = response_file_args.get(); |
| } |
| |
| std::string library_name; |
| |
| bool warnings_as_errors = false; |
| std::map<std::pair<Behavior, std::string>, std::fstream> outputs; |
| while (args->Remaining()) { |
| // Try to parse an output type. |
| std::string behavior_argument = args->Claim(); |
| std::fstream output_file; |
| if (behavior_argument == "--help") { |
| Usage(); |
| exit(0); |
| } else if (behavior_argument == "--json-schema") { |
| PrintJsonSchema(); |
| exit(0); |
| } else if (behavior_argument == "--werror") { |
| warnings_as_errors = true; |
| } else if (behavior_argument == "--c-header") { |
| std::string path = args->Claim(); |
| outputs.emplace(std::make_pair(Behavior::kCHeader, path), Open(path, std::ios::out)); |
| } else if (behavior_argument == "--c-client") { |
| std::string path = args->Claim(); |
| outputs.emplace(std::make_pair(Behavior::kCClient, path), Open(path, std::ios::out)); |
| } else if (behavior_argument == "--c-server") { |
| std::string path = args->Claim(); |
| outputs.emplace(std::make_pair(Behavior::kCServer, path), Open(path, std::ios::out)); |
| } else if (behavior_argument == "--tables") { |
| std::string path = args->Claim(); |
| outputs.emplace(std::make_pair(Behavior::kTables, path), Open(path, std::ios::out)); |
| } else if (behavior_argument == "--json") { |
| std::string path = args->Claim(); |
| outputs.emplace(std::make_pair(Behavior::kJSON, path), Open(path, std::ios::out)); |
| } else if (behavior_argument == "--name") { |
| library_name = args->Claim(); |
| } else if (behavior_argument == "--files") { |
| // Start parsing filenames. |
| break; |
| } else { |
| FailWithUsage("Unknown argument: %s\n", behavior_argument.data()); |
| } |
| } |
| |
| // Prepare source files. |
| std::vector<fidl::SourceManager> source_managers; |
| source_managers.push_back(fidl::SourceManager()); |
| std::string library_zx_data = fidl::LibraryZX::kData; |
| source_managers.back().AddSourceFile( |
| std::make_unique<fidl::SourceFile>(fidl::LibraryZX::kFilename, std::move(library_zx_data))); |
| source_managers.push_back(fidl::SourceManager()); |
| while (args->Remaining()) { |
| std::string arg = args->Claim(); |
| if (arg == "--files") { |
| source_managers.emplace_back(); |
| } else { |
| if (!source_managers.back().CreateSource(arg.data())) { |
| Fail("Couldn't read in source data from %s\n", arg.data()); |
| } |
| } |
| } |
| |
| // Ready. Set. Go. |
| fidl::ErrorReporter error_reporter(warnings_as_errors); |
| auto typespace = fidl::flat::Typespace::RootTypes(&error_reporter); |
| auto status = compile(&error_reporter, |
| &typespace, |
| library_name, |
| std::move(outputs), |
| std::move(source_managers)); |
| error_reporter.PrintReports(); |
| return status; |
| } |
| |
| int compile(fidl::ErrorReporter* error_reporter, |
| fidl::flat::Typespace* typespace, |
| std::string library_name, |
| std::map<std::pair<Behavior, std::string>, std::fstream> outputs, |
| std::vector<fidl::SourceManager> source_managers) { |
| fidl::flat::Libraries all_libraries; |
| const fidl::flat::Library* final_library = nullptr; |
| for (const auto& source_manager : source_managers) { |
| if (source_manager.sources().empty()) { |
| continue; |
| } |
| auto library = std::make_unique<fidl::flat::Library>(&all_libraries, error_reporter, typespace); |
| for (const auto& source_file : source_manager.sources()) { |
| if (!Parse(*source_file, error_reporter, library.get())) { |
| return 1; |
| } |
| } |
| if (!library->Compile()) { |
| return 1; |
| } |
| final_library = library.get(); |
| if (!all_libraries.Insert(std::move(library))) { |
| const auto& name = library->name(); |
| Fail("Multiple libraries with the same name: '%s'\n", |
| fidl::NameLibrary(name).data()); |
| } |
| } |
| if (!final_library) { |
| Fail("No library was produced.\n"); |
| } |
| auto unused_libraries_names = all_libraries.Unused(final_library); |
| // Because the sources of library zx are unconditionally included, we filter |
| // out this library here. We can remove this logic when zx is used in source |
| // like other libraries. |
| unused_libraries_names.erase(std::vector<std::string_view>{"zx"}); |
| if (unused_libraries_names.size() != 0) { |
| std::string message = "Unused libraries provided via --files: "; |
| bool first = true; |
| for (const auto& name : unused_libraries_names) { |
| if (first) { |
| first = false; |
| } else { |
| message.append(", "); |
| } |
| message.append(fidl::NameLibrary(name)); |
| } |
| message.append("\n"); |
| Fail(message.data()); |
| } |
| |
| // Verify that the produced library's name matches the expected name. |
| std::string final_name = fidl::NameLibrary(final_library->name()); |
| if (!library_name.empty() && final_name != library_name) { |
| Fail("Generated library '%s' did not match --name argument: %s\n", |
| final_name.data(), library_name.data()); |
| } |
| |
| // We recompile dependencies, and only emit output for the final |
| // library. |
| for (auto& output : outputs) { |
| auto& behavior = output.first.first; |
| auto& output_file = output.second; |
| |
| switch (behavior) { |
| case Behavior::kCHeader: { |
| fidl::CGenerator generator(final_library); |
| Write(generator.ProduceHeader(), std::move(output_file)); |
| break; |
| } |
| case Behavior::kCClient: { |
| fidl::CGenerator generator(final_library); |
| Write(generator.ProduceClient(), std::move(output_file)); |
| break; |
| } |
| case Behavior::kCServer: { |
| fidl::CGenerator generator(final_library); |
| Write(generator.ProduceServer(), std::move(output_file)); |
| break; |
| } |
| case Behavior::kTables: { |
| fidl::TablesGenerator generator(final_library); |
| Write(generator.Produce(), std::move(output_file)); |
| break; |
| } |
| case Behavior::kJSON: { |
| fidl::JSONGenerator generator(final_library); |
| Write(generator.Produce(), std::move(output_file)); |
| break; |
| } |
| } |
| } |
| return 0; |
| } |