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