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