flatc.cpp revision 5fa00630afaaf676595bebbc7348c960d6c0de5a
1/*
2 * Copyright 2014 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "flatbuffers/flatc.h"
18
19#include <list>
20
21#define FLATC_VERSION "1.7.1 (" __DATE__ ")"
22
23namespace flatbuffers {
24
25void FlatCompiler::ParseFile(
26    flatbuffers::Parser &parser,
27    const std::string &filename,
28    const std::string &contents,
29    std::vector<const char *> &include_directories) const {
30  auto local_include_directory = flatbuffers::StripFileName(filename);
31  include_directories.push_back(local_include_directory.c_str());
32  include_directories.push_back(nullptr);
33  if (!parser.Parse(contents.c_str(), &include_directories[0],
34                    filename.c_str()))
35    Error(parser.error_, false, false);
36  include_directories.pop_back();
37  include_directories.pop_back();
38}
39
40void FlatCompiler::Warn(const std::string &warn, bool show_exe_name) const {
41  params_.warn_fn(this, warn, show_exe_name);
42}
43
44void FlatCompiler::Error(const std::string &err, bool usage,
45                         bool show_exe_name) const {
46  params_.error_fn(this, err, usage, show_exe_name);
47}
48
49std::string FlatCompiler::GetUsageString(const char* program_name) const {
50  std::stringstream ss;
51  ss << "Usage: " << program_name << " [OPTION]... FILE... [-- FILE...]\n";
52  for (size_t i = 0; i < params_.num_generators; ++i) {
53    const Generator& g = params_.generators[i];
54
55    std::stringstream full_name;
56    full_name << std::setw(12) << std::left << g.generator_opt_long;
57    const char *name = g.generator_opt_short ? g.generator_opt_short : "  ";
58    const char *help = g.generator_help;
59
60    ss << "  " << full_name.str() << " " << name << "    " << help << ".\n";
61  }
62  ss <<
63      "  -o PATH            Prefix PATH to all generated files.\n"
64      "  -I PATH            Search for includes in the specified path.\n"
65      "  -M                 Print make rules for generated files.\n"
66      "  --version          Print the version number of flatc and exit.\n"
67      "  --strict-json      Strict JSON: field names must be / will be quoted,\n"
68      "                     no trailing commas in tables/vectors.\n"
69      "  --allow-non-utf8   Pass non-UTF-8 input through parser and emit nonstandard\n"
70      "                     \\x escapes in JSON. (Default is to raise parse error on\n"
71      "                     non-UTF-8 input.)\n"
72      "  --defaults-json    Output fields whose value is the default when\n"
73      "                     writing JSON\n"
74      "  --unknown-json     Allow fields in JSON that are not defined in the\n"
75      "                     schema. These fields will be discared when generating\n"
76      "                     binaries.\n"
77      "  --no-prefix        Don\'t prefix enum values with the enum type in C++.\n"
78      "  --scoped-enums     Use C++11 style scoped and strongly typed enums.\n"
79      "                     also implies --no-prefix.\n"
80      "  --gen-includes     (deprecated), this is the default behavior.\n"
81      "                     If the original behavior is required (no include\n"
82      "                     statements) use --no-includes.\n"
83      "  --no-includes      Don\'t generate include statements for included\n"
84      "                     schemas the generated file depends on (C++).\n"
85      "  --gen-mutable      Generate accessors that can mutate buffers in-place.\n"
86      "  --gen-onefile      Generate single output file for C#.\n"
87      "  --gen-name-strings Generate type name functions for C++.\n"
88      "  --escape-proto-ids Disable appending '_' in namespaces names.\n"
89      "  --gen-object-api   Generate an additional object-based API.\n"
90      "  --cpp-ptr-type T   Set object API pointer type (default std::unique_ptr)\n"
91      "  --cpp-str-type T   Set object API string type (default std::string)\n"
92      "                     T::c_str() and T::length() must be supported\n"
93      "  --object-prefix    Customise class prefix for C++ object-based API.\n"
94      "  --object-suffix    Customise class suffix for C++ object-based API.\n"
95      "                     Default value is \"T\"\n"
96      "  --no-js-exports    Removes Node.js style export lines in JS.\n"
97      "  --goog-js-export   Uses goog.exports* for closure compiler exporting in JS.\n"
98      "  --go-namespace     Generate the overrided namespace in Golang.\n"
99      "  --raw-binary       Allow binaries without file_indentifier to be read.\n"
100      "                     This may crash flatc given a mismatched schema.\n"
101      "  --proto            Input is a .proto, translate to .fbs.\n"
102      "  --grpc             Generate GRPC interfaces for the specified languages\n"
103      "  --schema           Serialize schemas instead of JSON (use with -b)\n"
104      "  --bfbs-comments    Add doc comments to the binary schema files.\n"
105      "  --conform FILE     Specify a schema the following schemas should be\n"
106      "                     an evolution of. Gives errors if not.\n"
107      "  --conform-includes Include path for the schema given with --conform\n"
108      "    PATH             \n"
109      "  --include-prefix   Prefix this path to any generated include statements.\n"
110      "    PATH\n"
111      "  --keep-prefix      Keep original prefix of schema include statement.\n"
112      "  --no-fb-import     Don't include flatbuffers import statement for TypeScript.\n"
113      "  --no-ts-reexport   Don't re-export imported dependencies for TypeScript.\n"
114      "FILEs may be schemas (must end in .fbs), or JSON files (conforming to preceding\n"
115      "schema). FILEs after the -- must be binary flatbuffer format files.\n"
116      "Output files are named using the base file name of the input,\n"
117      "and written to the current directory or the path given by -o.\n"
118      "example: " << program_name << " -c -b schema1.fbs schema2.fbs data.json\n";
119  return ss.str();
120}
121
122int FlatCompiler::Compile(int argc, const char** argv) {
123  if (params_.generators == nullptr || params_.num_generators == 0) {
124    return 0;
125  }
126
127  flatbuffers::IDLOptions opts;
128  std::string output_path;
129
130  bool any_generator = false;
131  bool print_make_rules = false;
132  bool raw_binary = false;
133  bool schema_binary = false;
134  bool grpc_enabled = false;
135  std::vector<std::string> filenames;
136  std::list<std::string> include_directories_storage;
137  std::vector<const char *> include_directories;
138  std::vector<const char *> conform_include_directories;
139  std::vector<bool> generator_enabled(params_.num_generators, false);
140  size_t binary_files_from = std::numeric_limits<size_t>::max();
141  std::string conform_to_schema;
142
143  for (int argi = 0; argi < argc; argi++) {
144    std::string arg = argv[argi];
145    if (arg[0] == '-') {
146      if (filenames.size() && arg[1] != '-')
147        Error("invalid option location: " + arg, true);
148      if (arg == "-o") {
149        if (++argi >= argc) Error("missing path following: " + arg, true);
150        output_path = flatbuffers::ConCatPathFileName(
151                        flatbuffers::PosixPath(argv[argi]), "");
152      } else if(arg == "-I") {
153        if (++argi >= argc) Error("missing path following" + arg, true);
154        include_directories_storage.push_back(
155                                      flatbuffers::PosixPath(argv[argi]));
156        include_directories.push_back(
157                              include_directories_storage.back().c_str());
158      } else if(arg == "--conform") {
159        if (++argi >= argc) Error("missing path following" + arg, true);
160        conform_to_schema = flatbuffers::PosixPath(argv[argi]);
161      } else if (arg == "--conform-includes") {
162        if (++argi >= argc) Error("missing path following" + arg, true);
163        include_directories_storage.push_back(
164                                      flatbuffers::PosixPath(argv[argi]));
165        conform_include_directories.push_back(
166                                    include_directories_storage.back().c_str());
167      } else if (arg == "--include-prefix") {
168        if (++argi >= argc) Error("missing path following" + arg, true);
169        opts.include_prefix = flatbuffers::ConCatPathFileName(
170                                flatbuffers::PosixPath(argv[argi]), "");
171      } else if(arg == "--keep-prefix") {
172        opts.keep_include_path = true;
173      } else if(arg == "--strict-json") {
174        opts.strict_json = true;
175      } else if(arg == "--allow-non-utf8") {
176        opts.allow_non_utf8 = true;
177      } else if(arg == "--no-js-exports") {
178        opts.skip_js_exports = true;
179      } else if(arg == "--goog-js-export") {
180        opts.use_goog_js_export_format = true;
181      } else if(arg == "--go-namespace") {
182        if (++argi >= argc) Error("missing golang namespace" + arg, true);
183        opts.go_namespace = argv[argi];
184      } else if(arg == "--defaults-json") {
185        opts.output_default_scalars_in_json = true;
186      } else if (arg == "--unknown-json") {
187        opts.skip_unexpected_fields_in_json = true;
188      } else if(arg == "--no-prefix") {
189        opts.prefixed_enums = false;
190      } else if(arg == "--scoped-enums") {
191        opts.prefixed_enums = false;
192        opts.scoped_enums = true;
193      } else if (arg == "--no-union-value-namespacing") {
194        opts.union_value_namespacing = false;
195      } else if(arg == "--gen-mutable") {
196        opts.mutable_buffer = true;
197      } else if(arg == "--gen-name-strings") {
198        opts.generate_name_strings = true;
199      } else if(arg == "--gen-object-api") {
200        opts.generate_object_based_api = true;
201      } else if (arg == "--cpp-ptr-type") {
202        if (++argi >= argc) Error("missing type following" + arg, true);
203        opts.cpp_object_api_pointer_type = argv[argi];
204      } else if (arg == "--cpp-str-type") {
205        if (++argi >= argc) Error("missing type following" + arg, true);
206        opts.cpp_object_api_string_type = argv[argi];
207      } else if (arg == "--object-prefix") {
208        if (++argi >= argc) Error("missing prefix following" + arg, true);
209        opts.object_prefix = argv[argi];
210      } else if (arg == "--object-suffix") {
211        if (++argi >= argc) Error("missing suffix following" + arg, true);
212        opts.object_suffix = argv[argi];
213      } else if(arg == "--gen-all") {
214        opts.generate_all = true;
215        opts.include_dependence_headers = false;
216      } else if(arg == "--gen-includes") {
217        // Deprecated, remove this option some time in the future.
218        printf("warning: --gen-includes is deprecated (it is now default)\n");
219      } else if(arg == "--no-includes") {
220        opts.include_dependence_headers = false;
221      } else if (arg == "--gen-onefile") {
222        opts.one_file = true;
223      } else if (arg == "--raw-binary") {
224        raw_binary = true;
225      } else if(arg == "--") {  // Separator between text and binary inputs.
226        binary_files_from = filenames.size();
227      } else if(arg == "--proto") {
228        opts.proto_mode = true;
229      } else if(arg == "--escape-proto-ids") {
230        opts.escape_proto_identifiers = true;
231      } else if(arg == "--schema") {
232        schema_binary = true;
233      } else if(arg == "-M") {
234        print_make_rules = true;
235      } else if(arg == "--version") {
236        printf("flatc version %s\n", FLATC_VERSION);
237        exit(0);
238      } else if(arg == "--grpc") {
239        grpc_enabled = true;
240      } else if(arg == "--bfbs-comments") {
241        opts.binary_schema_comments = true;
242      } else if(arg == "--no-fb-import") {
243        opts.skip_flatbuffers_import = true;
244      } else if(arg == "--no-ts-reexport") {
245        opts.reexport_ts_modules = false;
246      } else {
247        for (size_t i = 0; i < params_.num_generators; ++i) {
248          if (arg == params_.generators[i].generator_opt_long ||
249              (params_.generators[i].generator_opt_short &&
250               arg == params_.generators[i].generator_opt_short)) {
251            generator_enabled[i] = true;
252            any_generator = true;
253            opts.lang_to_generate |= params_.generators[i].lang;
254            goto found;
255          }
256        }
257        Error("unknown commandline argument: " + arg, true);
258        found:;
259      }
260    } else {
261      filenames.push_back(flatbuffers::PosixPath(argv[argi]));
262    }
263  }
264
265  if (!filenames.size()) Error("missing input files", false, true);
266
267  if (opts.proto_mode) {
268    if (any_generator)
269      Error("cannot generate code directly from .proto files", true);
270  } else if (!any_generator && conform_to_schema.empty()) {
271    Error("no options: specify at least one generator.", true);
272  }
273
274  flatbuffers::Parser conform_parser;
275  if (!conform_to_schema.empty()) {
276    std::string contents;
277    if (!flatbuffers::LoadFile(conform_to_schema.c_str(), true, &contents))
278      Error("unable to load schema: " + conform_to_schema);
279    ParseFile(conform_parser, conform_to_schema, contents,
280              conform_include_directories);
281  }
282
283  std::unique_ptr<flatbuffers::Parser> parser(new flatbuffers::Parser(opts));
284
285  for (auto file_it = filenames.begin();
286            file_it != filenames.end();
287          ++file_it) {
288    auto &filename = *file_it;
289    std::string contents;
290    if (!flatbuffers::LoadFile(filename.c_str(), true, &contents))
291      Error("unable to load file: " + filename);
292
293    bool is_binary = static_cast<size_t>(file_it - filenames.begin()) >=
294                     binary_files_from;
295    auto is_schema = flatbuffers::GetExtension(filename) == "fbs";
296    if (is_binary) {
297      parser->builder_.Clear();
298      parser->builder_.PushFlatBuffer(
299        reinterpret_cast<const uint8_t *>(contents.c_str()),
300        contents.length());
301      if (!raw_binary) {
302        // Generally reading binaries that do not correspond to the schema
303        // will crash, and sadly there's no way around that when the binary
304        // does not contain a file identifier.
305        // We'd expect that typically any binary used as a file would have
306        // such an identifier, so by default we require them to match.
307        if (!parser->file_identifier_.length()) {
308          Error("current schema has no file_identifier: cannot test if \"" +
309               filename +
310               "\" matches the schema, use --raw-binary to read this file"
311               " anyway.");
312        } else if (!flatbuffers::BufferHasIdentifier(contents.c_str(),
313                       parser->file_identifier_.c_str())) {
314          Error("binary \"" +
315               filename +
316               "\" does not have expected file_identifier \"" +
317               parser->file_identifier_ +
318               "\", use --raw-binary to read this file anyway.");
319        }
320      }
321    } else {
322      // Check if file contains 0 bytes.
323      if (contents.length() != strlen(contents.c_str())) {
324        Error("input file appears to be binary: " + filename, true);
325      }
326      if (is_schema) {
327        // If we're processing multiple schemas, make sure to start each
328        // one from scratch. If it depends on previous schemas it must do
329        // so explicitly using an include.
330        parser.reset(new flatbuffers::Parser(opts));
331      }
332      ParseFile(*parser.get(), filename, contents, include_directories);
333      if (!is_schema && !parser->builder_.GetSize()) {
334        // If a file doesn't end in .fbs, it must be json/binary. Ensure we
335        // didn't just parse a schema with a different extension.
336        Error("input file is neither json nor a .fbs (schema) file: " +
337              filename, true);
338      }
339      if (is_schema && !conform_to_schema.empty()) {
340        auto err = parser->ConformTo(conform_parser);
341        if (!err.empty()) Error("schemas don\'t conform: " + err);
342      }
343      if (schema_binary) {
344        parser->Serialize();
345        parser->file_extension_ = reflection::SchemaExtension();
346      }
347    }
348
349    std::string filebase = flatbuffers::StripPath(
350                             flatbuffers::StripExtension(filename));
351
352    for (size_t i = 0; i < params_.num_generators; ++i) {
353      parser->opts.lang = params_.generators[i].lang;
354      if (generator_enabled[i]) {
355        if (!print_make_rules) {
356          flatbuffers::EnsureDirExists(output_path);
357          if ((!params_.generators[i].schema_only || is_schema) &&
358              !params_.generators[i].generate(*parser.get(), output_path, filebase)) {
359            Error(std::string("Unable to generate ") +
360                  params_.generators[i].lang_name +
361                  " for " +
362                  filebase);
363          }
364        } else {
365          std::string make_rule = params_.generators[i].make_rule(
366              *parser.get(), output_path, filename);
367          if (!make_rule.empty())
368            printf("%s\n", flatbuffers::WordWrap(
369                make_rule, 80, " ", " \\").c_str());
370        }
371        if (grpc_enabled) {
372          if (params_.generators[i].generateGRPC != nullptr) {
373            if (!params_.generators[i].generateGRPC(*parser.get(), output_path,
374                                            filebase)) {
375              Error(std::string("Unable to generate GRPC interface for") +
376                    params_.generators[i].lang_name);
377            }
378          } else {
379            Warn(std::string("GRPC interface generator not implemented for ")
380                 + params_.generators[i].lang_name);
381          }
382        }
383      }
384    }
385
386    if (opts.proto_mode) GenerateFBS(*parser.get(), output_path, filebase);
387
388    // We do not want to generate code for the definitions in this file
389    // in any files coming up next.
390    parser->MarkGenerated();
391  }
392  return 0;
393}
394
395}  // namespace flatbuffers
396