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