8df2014e96e1fecefe12548c11312db0a67e2104
[platform/upstream/flatbuffers.git] / src / flatc.cpp
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 namespace flatbuffers {
22
23 const char *FLATC_VERSION() { return FLATBUFFERS_VERSION(); }
24
25 void FlatCompiler::ParseFile(
26     flatbuffers::Parser &parser, const std::string &filename,
27     const std::string &contents,
28     std::vector<const char *> &include_directories) const {
29   auto local_include_directory = flatbuffers::StripFileName(filename);
30   include_directories.push_back(local_include_directory.c_str());
31   include_directories.push_back(nullptr);
32   if (!parser.Parse(contents.c_str(), &include_directories[0],
33                     filename.c_str())) {
34     Error(parser.error_, false, false);
35   }
36   if (!parser.error_.empty()) { Warn(parser.error_, false); }
37   include_directories.pop_back();
38   include_directories.pop_back();
39 }
40
41 void FlatCompiler::LoadBinarySchema(flatbuffers::Parser &parser,
42                                     const std::string &filename,
43                                     const std::string &contents) {
44   if (!parser.Deserialize(reinterpret_cast<const uint8_t *>(contents.c_str()),
45                           contents.size())) {
46     Error("failed to load binary schema: " + filename, false, false);
47   }
48 }
49
50 void FlatCompiler::Warn(const std::string &warn, bool show_exe_name) const {
51   params_.warn_fn(this, warn, show_exe_name);
52 }
53
54 void FlatCompiler::Error(const std::string &err, bool usage,
55                          bool show_exe_name) const {
56   params_.error_fn(this, err, usage, show_exe_name);
57 }
58
59 std::string FlatCompiler::GetUsageString(const char *program_name) const {
60   std::stringstream ss;
61   ss << "Usage: " << program_name << " [OPTION]... FILE... [-- FILE...]\n";
62   for (size_t i = 0; i < params_.num_generators; ++i) {
63     const Generator &g = params_.generators[i];
64
65     std::stringstream full_name;
66     full_name << std::setw(16) << std::left << g.generator_opt_long;
67     const char *name = g.generator_opt_short ? g.generator_opt_short : "  ";
68     const char *help = g.generator_help;
69
70     ss << "  " << full_name.str() << " " << name << "    " << help << ".\n";
71   }
72   // clang-format off
73   ss <<
74     "  -o PATH                Prefix PATH to all generated files.\n"
75     "  -I PATH                Search for includes in the specified path.\n"
76     "  -M                     Print make rules for generated files.\n"
77     "  --version              Print the version number of flatc and exit.\n"
78     "  --strict-json          Strict JSON: field names must be / will be quoted,\n"
79     "                         no trailing commas in tables/vectors.\n"
80     "  --allow-non-utf8       Pass non-UTF-8 input through parser and emit nonstandard\n"
81     "                         \\x escapes in JSON. (Default is to raise parse error on\n"
82     "                         non-UTF-8 input.)\n"
83     "  --natural-utf8         Output strings with UTF-8 as human-readable strings.\n"
84     "                         By default, UTF-8 characters are printed as \\uXXXX escapes.\n"
85     "  --defaults-json        Output fields whose value is the default when\n"
86     "                         writing JSON\n"
87     "  --unknown-json         Allow fields in JSON that are not defined in the\n"
88     "                         schema. These fields will be discared when generating\n"
89     "                         binaries.\n"
90     "  --no-prefix            Don\'t prefix enum values with the enum type in C++.\n"
91     "  --scoped-enums         Use C++11 style scoped and strongly typed enums.\n"
92     "                         also implies --no-prefix.\n"
93     "  --gen-includes         (deprecated), this is the default behavior.\n"
94     "                         If the original behavior is required (no include\n"
95     "                         statements) use --no-includes.\n"
96     "  --no-includes          Don\'t generate include statements for included\n"
97     "                         schemas the generated file depends on (C++ / Python).\n"
98     "  --gen-mutable          Generate accessors that can mutate buffers in-place.\n"
99     "  --gen-onefile          Generate single output file for C# and Go.\n"
100     "  --gen-name-strings     Generate type name functions for C++.\n"
101     "  --gen-object-api       Generate an additional object-based API.\n"
102     "  --gen-compare          Generate operator== for object-based API types.\n"
103     "  --gen-nullable         Add Clang _Nullable for C++ pointer. or @Nullable for Java\n"
104     "  --java-checkerframe    work Add @Pure for Java.\n"
105     "  --gen-generated        Add @Generated annotation for Java\n"
106     "  --gen-all              Generate not just code for the current schema files,\n"
107     "                         but for all files it includes as well.\n"
108     "                         If the language uses a single file for output (by default\n"
109     "                         the case for C++ and JS), all code will end up in this one\n"
110     "                         file.\n"
111     "  --cpp-include          Adds an #include in generated file.\n"
112     "  --cpp-ptr-type T       Set object API pointer type (default std::unique_ptr).\n"
113     "  --cpp-str-type T       Set object API string type (default std::string).\n"
114     "                         T::c_str(), T::length() and T::empty() must be supported.\n"
115     "                         The custom type also needs to be constructible from std::string\n"
116     "                         (see the --cpp-str-flex-ctor option to change this behavior).\n"
117     "  --cpp-str-flex-ctor     Don't construct custom string types by passing std::string\n"
118     "                         from Flatbuffers, but (char* + length).\n"
119     "  --cpp-std CPP_STD      Generate a C++ code using features of selected C++ standard.\n"
120     "                         Supported CPP_STD values:\n"
121     "                          * 'c++0x' - generate code compatible with old compilers;\n"
122     "                          * 'c++11' - use C++11 code generator (default);\n"
123     "                          * 'c++17' - use C++17 features in generated code (experimental).\n"
124     "  --object-prefix        Customise class prefix for C++ object-based API.\n"
125     "  --object-suffix        Customise class suffix for C++ object-based API.\n"
126     "                         Default value is \"T\".\n"
127     "  --no-js-exports        Removes Node.js style export lines in JS.\n"
128     "  --goog-js-export       Uses goog.exports* for closure compiler exporting in JS.\n"
129     "  --es6-js-export        Uses ECMAScript 6 export style lines in JS.\n"
130     "  --go-namespace         Generate the overrided namespace in Golang.\n"
131     "  --go-import            Generate the overrided import for flatbuffers in Golang\n"
132     "                         (default is \"github.com/google/flatbuffers/go\").\n"
133     "  --raw-binary           Allow binaries without file_indentifier to be read.\n"
134     "                         This may crash flatc given a mismatched schema.\n"
135     "  --size-prefixed        Input binaries are size prefixed buffers.\n"
136     "  --proto                Input is a .proto, translate to .fbs.\n"
137     "  --oneof-union          Translate .proto oneofs to flatbuffer unions.\n"
138     "  --grpc                 Generate GRPC interfaces for the specified languages.\n"
139     "  --schema               Serialize schemas instead of JSON (use with -b).\n"
140     "  --bfbs-comments        Add doc comments to the binary schema files.\n"
141     "  --bfbs-builtins        Add builtin attributes to the binary schema files.\n"
142     "  --conform FILE         Specify a schema the following schemas should be\n"
143     "                         an evolution of. Gives errors if not.\n"
144     "  --conform-includes     Include path for the schema given with --conform PATH\n"
145     "  --include-prefix       Prefix this path to any generated include statements.\n"
146     "    PATH\n"
147     "  --keep-prefix          Keep original prefix of schema include statement.\n"
148     "  --no-fb-import         Don't include flatbuffers import statement for TypeScript.\n"
149     "  --no-ts-reexport       Don't re-export imported dependencies for TypeScript.\n"
150     "  --short-names          Use short function names for JS and TypeScript.\n"
151     "  --reflect-types        Add minimal type reflection to code generation.\n"
152     "  --reflect-names        Add minimal type/name reflection.\n"
153     "  --root-type T          Select or override the default root_type\n"
154     "  --force-defaults       Emit default values in binary output from JSON\n"
155     "  --force-empty          When serializing from object API representation,\n"
156     "                         force strings and vectors to empty rather than null.\n"
157     "  --force-empty-vectors  When serializing from object API representation,\n"
158     "                         force vectors to empty rather than null.\n"
159     "  --flexbuffers          Used with \"binary\" and \"json\" options, it generates\n"
160     "                         data using schema-less FlexBuffers.\n"
161     "FILEs may be schemas (must end in .fbs), binary schemas (must end in .bfbs),\n"
162     "or JSON files (conforming to preceding schema). FILEs after the -- must be\n"
163     "binary flatbuffer format files.\n"
164     "Output files are named using the base file name of the input,\n"
165     "and written to the current directory or the path given by -o.\n"
166     "example: " << program_name << " -c -b schema1.fbs schema2.fbs data.json\n";
167   // clang-format on
168   return ss.str();
169 }
170
171 int FlatCompiler::Compile(int argc, const char **argv) {
172   if (params_.generators == nullptr || params_.num_generators == 0) {
173     return 0;
174   }
175
176   flatbuffers::IDLOptions opts;
177   std::string output_path;
178
179   bool any_generator = false;
180   bool print_make_rules = false;
181   bool raw_binary = false;
182   bool schema_binary = false;
183   bool grpc_enabled = false;
184   std::vector<std::string> filenames;
185   std::list<std::string> include_directories_storage;
186   std::vector<const char *> include_directories;
187   std::vector<const char *> conform_include_directories;
188   std::vector<bool> generator_enabled(params_.num_generators, false);
189   size_t binary_files_from = std::numeric_limits<size_t>::max();
190   std::string conform_to_schema;
191
192   for (int argi = 0; argi < argc; argi++) {
193     std::string arg = argv[argi];
194     if (arg[0] == '-') {
195       if (filenames.size() && arg[1] != '-')
196         Error("invalid option location: " + arg, true);
197       if (arg == "-o") {
198         if (++argi >= argc) Error("missing path following: " + arg, true);
199         output_path = flatbuffers::ConCatPathFileName(
200             flatbuffers::PosixPath(argv[argi]), "");
201       } else if (arg == "-I") {
202         if (++argi >= argc) Error("missing path following" + arg, true);
203         include_directories_storage.push_back(
204             flatbuffers::PosixPath(argv[argi]));
205         include_directories.push_back(
206             include_directories_storage.back().c_str());
207       } else if (arg == "--conform") {
208         if (++argi >= argc) Error("missing path following" + arg, true);
209         conform_to_schema = flatbuffers::PosixPath(argv[argi]);
210       } else if (arg == "--conform-includes") {
211         if (++argi >= argc) Error("missing path following" + arg, true);
212         include_directories_storage.push_back(
213             flatbuffers::PosixPath(argv[argi]));
214         conform_include_directories.push_back(
215             include_directories_storage.back().c_str());
216       } else if (arg == "--include-prefix") {
217         if (++argi >= argc) Error("missing path following" + arg, true);
218         opts.include_prefix = flatbuffers::ConCatPathFileName(
219             flatbuffers::PosixPath(argv[argi]), "");
220       } else if (arg == "--keep-prefix") {
221         opts.keep_include_path = true;
222       } else if (arg == "--strict-json") {
223         opts.strict_json = true;
224       } else if (arg == "--allow-non-utf8") {
225         opts.allow_non_utf8 = true;
226       } else if (arg == "--natural-utf8") {
227         opts.natural_utf8 = true;
228       } else if (arg == "--no-js-exports") {
229         opts.skip_js_exports = true;
230       } else if (arg == "--goog-js-export") {
231         opts.use_goog_js_export_format = true;
232         opts.use_ES6_js_export_format = false;
233       } else if (arg == "--es6-js-export") {
234         opts.use_goog_js_export_format = false;
235         opts.use_ES6_js_export_format = true;
236       } else if (arg == "--go-namespace") {
237         if (++argi >= argc) Error("missing golang namespace" + arg, true);
238         opts.go_namespace = argv[argi];
239       } else if (arg == "--go-import") {
240         if (++argi >= argc) Error("missing golang import" + arg, true);
241         opts.go_import = argv[argi];
242       } else if (arg == "--defaults-json") {
243         opts.output_default_scalars_in_json = true;
244       } else if (arg == "--unknown-json") {
245         opts.skip_unexpected_fields_in_json = true;
246       } else if (arg == "--no-prefix") {
247         opts.prefixed_enums = false;
248       } else if (arg == "--scoped-enums") {
249         opts.prefixed_enums = false;
250         opts.scoped_enums = true;
251       } else if (arg == "--no-union-value-namespacing") {
252         opts.union_value_namespacing = false;
253       } else if (arg == "--gen-mutable") {
254         opts.mutable_buffer = true;
255       } else if (arg == "--gen-name-strings") {
256         opts.generate_name_strings = true;
257       } else if (arg == "--gen-object-api") {
258         opts.generate_object_based_api = true;
259       } else if (arg == "--gen-compare") {
260         opts.gen_compare = true;
261       } else if (arg == "--cpp-include") {
262         if (++argi >= argc) Error("missing include following" + arg, true);
263         opts.cpp_includes.push_back(argv[argi]);
264       } else if (arg == "--cpp-ptr-type") {
265         if (++argi >= argc) Error("missing type following" + arg, true);
266         opts.cpp_object_api_pointer_type = argv[argi];
267       } else if (arg == "--cpp-str-type") {
268         if (++argi >= argc) Error("missing type following" + arg, true);
269         opts.cpp_object_api_string_type = argv[argi];
270       } else if (arg == "--cpp-str-flex-ctor") {
271         opts.cpp_object_api_string_flexible_constructor = true;
272       } else if (arg == "--gen-nullable") {
273         opts.gen_nullable = true;
274       } else if (arg == "--java-checkerframework") {
275         opts.java_checkerframework = true;
276       } else if (arg == "--gen-generated") {
277         opts.gen_generated = true;
278       } else if (arg == "--object-prefix") {
279         if (++argi >= argc) Error("missing prefix following" + arg, true);
280         opts.object_prefix = argv[argi];
281       } else if (arg == "--object-suffix") {
282         if (++argi >= argc) Error("missing suffix following" + arg, true);
283         opts.object_suffix = argv[argi];
284       } else if (arg == "--gen-all") {
285         opts.generate_all = true;
286         opts.include_dependence_headers = false;
287       } else if (arg == "--gen-includes") {
288         // Deprecated, remove this option some time in the future.
289         Warn("warning: --gen-includes is deprecated (it is now default)\n");
290       } else if (arg == "--no-includes") {
291         opts.include_dependence_headers = false;
292       } else if (arg == "--gen-onefile") {
293         opts.one_file = true;
294       } else if (arg == "--raw-binary") {
295         raw_binary = true;
296       } else if (arg == "--size-prefixed") {
297         opts.size_prefixed = true;
298       } else if (arg == "--") {  // Separator between text and binary inputs.
299         binary_files_from = filenames.size();
300       } else if (arg == "--proto") {
301         opts.proto_mode = true;
302       } else if (arg == "--oneof-union") {
303         opts.proto_oneof_union = true;
304       } else if (arg == "--schema") {
305         schema_binary = true;
306       } else if (arg == "-M") {
307         print_make_rules = true;
308       } else if (arg == "--version") {
309         printf("flatc version %s\n", FLATC_VERSION());
310         exit(0);
311       } else if (arg == "--grpc") {
312         grpc_enabled = true;
313       } else if (arg == "--bfbs-comments") {
314         opts.binary_schema_comments = true;
315       } else if (arg == "--bfbs-builtins") {
316         opts.binary_schema_builtins = true;
317       } else if (arg == "--no-fb-import") {
318         opts.skip_flatbuffers_import = true;
319       } else if (arg == "--no-ts-reexport") {
320         opts.reexport_ts_modules = false;
321       } else if (arg == "--short-names") {
322         opts.js_ts_short_names = true;
323       } else if (arg == "--reflect-types") {
324         opts.mini_reflect = IDLOptions::kTypes;
325       } else if (arg == "--reflect-names") {
326         opts.mini_reflect = IDLOptions::kTypesAndNames;
327       } else if (arg == "--root-type") {
328         if (++argi >= argc) Error("missing type following" + arg, true);
329         opts.root_type = argv[argi];
330       } else if (arg == "--force-defaults") {
331         opts.force_defaults = true;
332       } else if (arg == "--force-empty") {
333         opts.set_empty_strings_to_null = false;
334         opts.set_empty_vectors_to_null = false;
335       } else if (arg == "--force-empty-vectors") {
336         opts.set_empty_vectors_to_null = false;
337       } else if (arg == "--java-primitive-has-method") {
338         opts.java_primitive_has_method = true;
339       } else if (arg == "--flexbuffers") {
340         opts.use_flexbuffers = true;
341       } else if(arg == "--cpp-std") {
342         if (++argi >= argc) Error("missing C++ standard specification" + arg, true);
343         opts.cpp_std = argv[argi];
344       } else {
345         for (size_t i = 0; i < params_.num_generators; ++i) {
346           if (arg == params_.generators[i].generator_opt_long ||
347               (params_.generators[i].generator_opt_short &&
348                arg == params_.generators[i].generator_opt_short)) {
349             generator_enabled[i] = true;
350             any_generator = true;
351             opts.lang_to_generate |= params_.generators[i].lang;
352             goto found;
353           }
354         }
355         Error("unknown commandline argument: " + arg, true);
356       found:;
357       }
358     } else {
359       filenames.push_back(flatbuffers::PosixPath(argv[argi]));
360     }
361   }
362
363   if (!filenames.size()) Error("missing input files", false, true);
364
365   if (opts.proto_mode) {
366     if (any_generator)
367       Error("cannot generate code directly from .proto files", true);
368   } else if (!any_generator && conform_to_schema.empty()) {
369     Error("no options: specify at least one generator.", true);
370   }
371
372   flatbuffers::Parser conform_parser;
373   if (!conform_to_schema.empty()) {
374     std::string contents;
375     if (!flatbuffers::LoadFile(conform_to_schema.c_str(), true, &contents))
376       Error("unable to load schema: " + conform_to_schema);
377
378     if (flatbuffers::GetExtension(conform_to_schema) ==
379         reflection::SchemaExtension()) {
380       LoadBinarySchema(conform_parser, conform_to_schema, contents);
381     } else {
382       ParseFile(conform_parser, conform_to_schema, contents,
383                 conform_include_directories);
384     }
385   }
386
387   std::unique_ptr<flatbuffers::Parser> parser(new flatbuffers::Parser(opts));
388
389   for (auto file_it = filenames.begin(); file_it != filenames.end();
390        ++file_it) {
391     auto &filename = *file_it;
392     std::string contents;
393     if (!flatbuffers::LoadFile(filename.c_str(), true, &contents))
394       Error("unable to load file: " + filename);
395
396     bool is_binary =
397         static_cast<size_t>(file_it - filenames.begin()) >= binary_files_from;
398     auto ext = flatbuffers::GetExtension(filename);
399     auto is_schema = ext == "fbs" || ext == "proto";
400     auto is_binary_schema = ext == reflection::SchemaExtension();
401     if (is_binary) {
402       parser->builder_.Clear();
403       parser->builder_.PushFlatBuffer(
404           reinterpret_cast<const uint8_t *>(contents.c_str()),
405           contents.length());
406       if (!raw_binary) {
407         // Generally reading binaries that do not correspond to the schema
408         // will crash, and sadly there's no way around that when the binary
409         // does not contain a file identifier.
410         // We'd expect that typically any binary used as a file would have
411         // such an identifier, so by default we require them to match.
412         if (!parser->file_identifier_.length()) {
413           Error("current schema has no file_identifier: cannot test if \"" +
414                 filename +
415                 "\" matches the schema, use --raw-binary to read this file"
416                 " anyway.");
417         } else if (!flatbuffers::BufferHasIdentifier(
418                        contents.c_str(), parser->file_identifier_.c_str(),
419                        opts.size_prefixed)) {
420           Error("binary \"" + filename +
421                 "\" does not have expected file_identifier \"" +
422                 parser->file_identifier_ +
423                 "\", use --raw-binary to read this file anyway.");
424         }
425       }
426     } else {
427       // Check if file contains 0 bytes.
428       if (!opts.use_flexbuffers && !is_binary_schema &&
429           contents.length() != strlen(contents.c_str())) {
430         Error("input file appears to be binary: " + filename, true);
431       }
432       if (is_schema) {
433         // If we're processing multiple schemas, make sure to start each
434         // one from scratch. If it depends on previous schemas it must do
435         // so explicitly using an include.
436         parser.reset(new flatbuffers::Parser(opts));
437       }
438       if (is_binary_schema) {
439         LoadBinarySchema(*parser.get(), filename, contents);
440       }
441       if (opts.use_flexbuffers) {
442         if (opts.lang_to_generate == IDLOptions::kJson) {
443           parser->flex_root_ = flexbuffers::GetRoot(
444               reinterpret_cast<const uint8_t *>(contents.c_str()),
445               contents.size());
446         } else {
447           parser->flex_builder_.Clear();
448           ParseFile(*parser.get(), filename, contents, include_directories);
449         }
450       } else {
451         ParseFile(*parser.get(), filename, contents, include_directories);
452         if (!is_schema && !parser->builder_.GetSize()) {
453           // If a file doesn't end in .fbs, it must be json/binary. Ensure we
454           // didn't just parse a schema with a different extension.
455           Error("input file is neither json nor a .fbs (schema) file: " +
456                     filename,
457                 true);
458         }
459       }
460       if ((is_schema || is_binary_schema) && !conform_to_schema.empty()) {
461         auto err = parser->ConformTo(conform_parser);
462         if (!err.empty()) Error("schemas don\'t conform: " + err);
463       }
464       if (schema_binary) {
465         parser->Serialize();
466         parser->file_extension_ = reflection::SchemaExtension();
467       }
468     }
469
470     std::string filebase =
471         flatbuffers::StripPath(flatbuffers::StripExtension(filename));
472
473     for (size_t i = 0; i < params_.num_generators; ++i) {
474       parser->opts.lang = params_.generators[i].lang;
475       if (generator_enabled[i]) {
476         if (!print_make_rules) {
477           flatbuffers::EnsureDirExists(output_path);
478           if ((!params_.generators[i].schema_only ||
479                (is_schema || is_binary_schema)) &&
480               !params_.generators[i].generate(*parser.get(), output_path,
481                                               filebase)) {
482             Error(std::string("Unable to generate ") +
483                   params_.generators[i].lang_name + " for " + filebase);
484           }
485         } else {
486           if (params_.generators[i].make_rule == nullptr) {
487             Error(std::string("Cannot generate make rule for ") +
488                   params_.generators[i].lang_name);
489           } else {
490             std::string make_rule = params_.generators[i].make_rule(
491                 *parser.get(), output_path, filename);
492             if (!make_rule.empty())
493               printf("%s\n",
494                      flatbuffers::WordWrap(make_rule, 80, " ", " \\").c_str());
495           }
496         }
497         if (grpc_enabled) {
498           if (params_.generators[i].generateGRPC != nullptr) {
499             if (!params_.generators[i].generateGRPC(*parser.get(), output_path,
500                                                     filebase)) {
501               Error(std::string("Unable to generate GRPC interface for") +
502                     params_.generators[i].lang_name);
503             }
504           } else {
505             Warn(std::string("GRPC interface generator not implemented for ") +
506                  params_.generators[i].lang_name);
507           }
508         }
509       }
510     }
511
512     if (!opts.root_type.empty()) {
513       if (!parser->SetRootType(opts.root_type.c_str()))
514         Error("unknown root type: " + opts.root_type);
515       else if (parser->root_struct_def_->fixed)
516         Error("root type must be a table");
517     }
518
519     if (opts.proto_mode) GenerateFBS(*parser.get(), output_path, filebase);
520
521     // We do not want to generate code for the definitions in this file
522     // in any files coming up next.
523     parser->MarkGenerated();
524   }
525   return 0;
526 }
527
528 }  // namespace flatbuffers