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