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