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