flatc.cpp revision a6a3f5925393146e879ba03d919a3380b9af7994
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.0 (" __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      "  --no-js-exports    Removes Node.js style export lines in JS.\n"
94      "  --goog-js-export   Uses goog.exports* for closure compiler exporting in JS.\n"
95      "  --go-namespace     Generate the overrided namespace in Golang.\n"
96      "  --raw-binary       Allow binaries without file_indentifier to be read.\n"
97      "                     This may crash flatc given a mismatched schema.\n"
98      "  --proto            Input is a .proto, translate to .fbs.\n"
99      "  --grpc             Generate GRPC interfaces for the specified languages\n"
100      "  --schema           Serialize schemas instead of JSON (use with -b)\n"
101      "  --bfbs-comments    Add doc comments to the binary schema files.\n"
102      "  --conform FILE     Specify a schema the following schemas should be\n"
103      "                     an evolution of. Gives errors if not.\n"
104      "  --conform-includes Include path for the schema given with --conform\n"
105      "    PATH             \n"
106      "  --include-prefix   Prefix this path to any generated include statements.\n"
107      "    PATH\n"
108      "  --keep-prefix      Keep original prefix of schema include statement.\n"
109      "  --no-fb-import     Don't include flatbuffers import statement for TypeScript.\n"
110      "  --no-ts-reexport   Don't re-export imported dependencies for TypeScript.\n"
111      "FILEs may be schemas (must end in .fbs), or JSON files (conforming to preceding\n"
112      "schema). FILEs after the -- must be binary flatbuffer format files.\n"
113      "Output files are named using the base file name of the input,\n"
114      "and written to the current directory or the path given by -o.\n"
115      "example: " << program_name << " -c -b schema1.fbs schema2.fbs data.json\n";
116  return ss.str();
117}
118
119int FlatCompiler::Compile(int argc, const char** argv) {
120  if (params_.generators == nullptr || params_.num_generators == 0) {
121    return 0;
122  }
123
124  flatbuffers::IDLOptions opts;
125  std::string output_path;
126
127  bool any_generator = false;
128  bool print_make_rules = false;
129  bool raw_binary = false;
130  bool schema_binary = false;
131  bool grpc_enabled = false;
132  std::vector<std::string> filenames;
133  std::list<std::string> include_directories_storage;
134  std::vector<const char *> include_directories;
135  std::vector<const char *> conform_include_directories;
136  std::vector<bool> generator_enabled(params_.num_generators, false);
137  size_t binary_files_from = std::numeric_limits<size_t>::max();
138  std::string conform_to_schema;
139
140  for (int argi = 0; argi < argc; argi++) {
141    std::string arg = argv[argi];
142    if (arg[0] == '-') {
143      if (filenames.size() && arg[1] != '-')
144        Error("invalid option location: " + arg, true);
145      if (arg == "-o") {
146        if (++argi >= argc) Error("missing path following: " + arg, true);
147        output_path = flatbuffers::ConCatPathFileName(
148                        flatbuffers::PosixPath(argv[argi]), "");
149      } else if(arg == "-I") {
150        if (++argi >= argc) Error("missing path following" + arg, true);
151        include_directories_storage.push_back(
152                                      flatbuffers::PosixPath(argv[argi]));
153        include_directories.push_back(
154                              include_directories_storage.back().c_str());
155      } else if(arg == "--conform") {
156        if (++argi >= argc) Error("missing path following" + arg, true);
157        conform_to_schema = flatbuffers::PosixPath(argv[argi]);
158      } else if (arg == "--conform-includes") {
159        if (++argi >= argc) Error("missing path following" + arg, true);
160        include_directories_storage.push_back(
161                                      flatbuffers::PosixPath(argv[argi]));
162        conform_include_directories.push_back(
163                                    include_directories_storage.back().c_str());
164      } else if (arg == "--include-prefix") {
165        if (++argi >= argc) Error("missing path following" + arg, true);
166        opts.include_prefix = flatbuffers::ConCatPathFileName(
167                                flatbuffers::PosixPath(argv[argi]), "");
168      } else if(arg == "--keep-prefix") {
169        opts.keep_include_path = true;
170      } else if(arg == "--strict-json") {
171        opts.strict_json = true;
172      } else if(arg == "--allow-non-utf8") {
173        opts.allow_non_utf8 = true;
174      } else if(arg == "--no-js-exports") {
175        opts.skip_js_exports = true;
176      } else if(arg == "--goog-js-export") {
177        opts.use_goog_js_export_format = true;
178      } else if(arg == "--go-namespace") {
179        if (++argi >= argc) Error("missing golang namespace" + arg, true);
180        opts.go_namespace = argv[argi];
181      } else if(arg == "--defaults-json") {
182        opts.output_default_scalars_in_json = true;
183      } else if (arg == "--unknown-json") {
184        opts.skip_unexpected_fields_in_json = true;
185      } else if(arg == "--no-prefix") {
186        opts.prefixed_enums = false;
187      } else if(arg == "--scoped-enums") {
188        opts.prefixed_enums = false;
189        opts.scoped_enums = true;
190      } else if (arg == "--no-union-value-namespacing") {
191        opts.union_value_namespacing = false;
192      } else if(arg == "--gen-mutable") {
193        opts.mutable_buffer = true;
194      } else if(arg == "--gen-name-strings") {
195        opts.generate_name_strings = true;
196      } else if(arg == "--gen-object-api") {
197        opts.generate_object_based_api = true;
198      } else if (arg == "--cpp-ptr-type") {
199        if (++argi >= argc) Error("missing type following" + arg, true);
200        opts.cpp_object_api_pointer_type = argv[argi];
201      } else if (arg == "--cpp-str-type") {
202        if (++argi >= argc) Error("missing type following" + arg, true);
203        opts.cpp_object_api_string_type = argv[argi];
204      } else if(arg == "--gen-all") {
205        opts.generate_all = true;
206        opts.include_dependence_headers = false;
207      } else if(arg == "--gen-includes") {
208        // Deprecated, remove this option some time in the future.
209        printf("warning: --gen-includes is deprecated (it is now default)\n");
210      } else if(arg == "--no-includes") {
211        opts.include_dependence_headers = false;
212      } else if (arg == "--gen-onefile") {
213        opts.one_file = true;
214      } else if (arg == "--raw-binary") {
215        raw_binary = true;
216      } else if(arg == "--") {  // Separator between text and binary inputs.
217        binary_files_from = filenames.size();
218      } else if(arg == "--proto") {
219        opts.proto_mode = true;
220      } else if(arg == "--escape-proto-ids") {
221        opts.escape_proto_identifiers = true;
222      } else if(arg == "--schema") {
223        schema_binary = true;
224      } else if(arg == "-M") {
225        print_make_rules = true;
226      } else if(arg == "--version") {
227        printf("flatc version %s\n", FLATC_VERSION);
228        exit(0);
229      } else if(arg == "--grpc") {
230        grpc_enabled = true;
231      } else if(arg == "--bfbs-comments") {
232        opts.binary_schema_comments = true;
233      } else if(arg == "--no-fb-import") {
234        opts.skip_flatbuffers_import = true;
235      } else if(arg == "--no-ts-reexport") {
236        opts.reexport_ts_modules = false;
237      } else {
238        for (size_t i = 0; i < params_.num_generators; ++i) {
239          if (arg == params_.generators[i].generator_opt_long ||
240              (params_.generators[i].generator_opt_short &&
241               arg == params_.generators[i].generator_opt_short)) {
242            generator_enabled[i] = true;
243            any_generator = true;
244            opts.lang_to_generate |= params_.generators[i].lang;
245            goto found;
246          }
247        }
248        Error("unknown commandline argument: " + arg, true);
249        found:;
250      }
251    } else {
252      filenames.push_back(flatbuffers::PosixPath(argv[argi]));
253    }
254  }
255
256  if (!filenames.size()) Error("missing input files", false, true);
257
258  if (opts.proto_mode) {
259    if (any_generator)
260      Error("cannot generate code directly from .proto files", true);
261  } else if (!any_generator && conform_to_schema.empty()) {
262    Error("no options: specify at least one generator.", true);
263  }
264
265  flatbuffers::Parser conform_parser;
266  if (!conform_to_schema.empty()) {
267    std::string contents;
268    if (!flatbuffers::LoadFile(conform_to_schema.c_str(), true, &contents))
269      Error("unable to load schema: " + conform_to_schema);
270    ParseFile(conform_parser, conform_to_schema, contents,
271              conform_include_directories);
272  }
273
274  std::unique_ptr<flatbuffers::Parser> parser(new flatbuffers::Parser(opts));
275
276  for (auto file_it = filenames.begin();
277            file_it != filenames.end();
278          ++file_it) {
279    auto &filename = *file_it;
280    std::string contents;
281    if (!flatbuffers::LoadFile(filename.c_str(), true, &contents))
282      Error("unable to load file: " + filename);
283
284    bool is_binary = static_cast<size_t>(file_it - filenames.begin()) >=
285                     binary_files_from;
286    auto is_schema = flatbuffers::GetExtension(filename) == "fbs";
287    if (is_binary) {
288      parser->builder_.Clear();
289      parser->builder_.PushFlatBuffer(
290        reinterpret_cast<const uint8_t *>(contents.c_str()),
291        contents.length());
292      if (!raw_binary) {
293        // Generally reading binaries that do not correspond to the schema
294        // will crash, and sadly there's no way around that when the binary
295        // does not contain a file identifier.
296        // We'd expect that typically any binary used as a file would have
297        // such an identifier, so by default we require them to match.
298        if (!parser->file_identifier_.length()) {
299          Error("current schema has no file_identifier: cannot test if \"" +
300               filename +
301               "\" matches the schema, use --raw-binary to read this file"
302               " anyway.");
303        } else if (!flatbuffers::BufferHasIdentifier(contents.c_str(),
304                       parser->file_identifier_.c_str())) {
305          Error("binary \"" +
306               filename +
307               "\" does not have expected file_identifier \"" +
308               parser->file_identifier_ +
309               "\", use --raw-binary to read this file anyway.");
310        }
311      }
312    } else {
313      // Check if file contains 0 bytes.
314      if (contents.length() != strlen(contents.c_str())) {
315        Error("input file appears to be binary: " + filename, true);
316      }
317      if (is_schema) {
318        // If we're processing multiple schemas, make sure to start each
319        // one from scratch. If it depends on previous schemas it must do
320        // so explicitly using an include.
321        parser.reset(new flatbuffers::Parser(opts));
322      }
323      ParseFile(*parser.get(), filename, contents, include_directories);
324      if (!is_schema && !parser->builder_.GetSize()) {
325        // If a file doesn't end in .fbs, it must be json/binary. Ensure we
326        // didn't just parse a schema with a different extension.
327        Error("input file is neither json nor a .fbs (schema) file: " +
328              filename, true);
329      }
330      if (is_schema && !conform_to_schema.empty()) {
331        auto err = parser->ConformTo(conform_parser);
332        if (!err.empty()) Error("schemas don\'t conform: " + err);
333      }
334      if (schema_binary) {
335        parser->Serialize();
336        parser->file_extension_ = reflection::SchemaExtension();
337      }
338    }
339
340    std::string filebase = flatbuffers::StripPath(
341                             flatbuffers::StripExtension(filename));
342
343    for (size_t i = 0; i < params_.num_generators; ++i) {
344      parser->opts.lang = params_.generators[i].lang;
345      if (generator_enabled[i]) {
346        if (!print_make_rules) {
347          flatbuffers::EnsureDirExists(output_path);
348          if ((!params_.generators[i].schema_only || is_schema) &&
349              !params_.generators[i].generate(*parser.get(), output_path, filebase)) {
350            Error(std::string("Unable to generate ") +
351                  params_.generators[i].lang_name +
352                  " for " +
353                  filebase);
354          }
355        } else {
356          std::string make_rule = params_.generators[i].make_rule(
357              *parser.get(), output_path, filename);
358          if (!make_rule.empty())
359            printf("%s\n", flatbuffers::WordWrap(
360                make_rule, 80, " ", " \\").c_str());
361        }
362        if (grpc_enabled) {
363          if (params_.generators[i].generateGRPC != nullptr) {
364            if (!params_.generators[i].generateGRPC(*parser.get(), output_path,
365                                            filebase)) {
366              Error(std::string("Unable to generate GRPC interface for") +
367                    params_.generators[i].lang_name);
368            }
369          } else {
370            Warn(std::string("GRPC interface generator not implemented for ")
371                 + params_.generators[i].lang_name);
372          }
373        }
374      }
375    }
376
377    if (opts.proto_mode) GenerateFBS(*parser.get(), output_path, filebase);
378
379    // We do not want to generate code for the definitions in this file
380    // in any files coming up next.
381    parser->MarkGenerated();
382  }
383  return 0;
384}
385
386}  // namespace flatbuffers
387