3 # This file is part of INAV.
5 # author: Alberto Garcia Hierro <alberto@garciahierro.com>
7 # This Source Code Form is subject to the terms of the Mozilla Public
8 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
9 # You can obtain one at http://mozilla.org/MPL/2.0/.
11 # Alternatively, the contents of this file may be used under the terms
12 # of the GNU General Public License Version 3, as described below:
14 # This file is free software: you may copy, redistribute and/or modify
15 # it under the terms of the GNU General Public License as published by the
16 # Free Software Foundation, either version 3 of the License, or (at your
17 # option) any later version.
19 # This file is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
22 # Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see http://www.gnu.org/licenses/.
35 require_relative 'compiler'
40 SETTINGS_WORDS_BITS_PER_CHAR = 5
52 self.kind_of?(Integer) || self.kind_of?(Float)
58 true if Float(self) rescue false
64 self << [b].pack("C*")
69 write_byte((x & 0xFF) | 0x80)
76 return string.bytes.to_s.sub('[', '{').sub(']', '}')
81 attr_reader :max_length
82 attr_reader :max_word_length
84 def initialize(names, max_length)
86 @max_length = max_length
87 # Key is word, value is number of uses
89 # Most used words first
91 # Words that shouldn't be split because
92 # their encoding is too long
94 # Key is the name, value is its encoding
103 def uses_byte_indexing
111 def estimated_size(settings_count)
114 @words.each do |word, count|
115 size += (word.length + 1) * (5/8.0)
116 if word.length > @max_word_length
117 @max_word_length = word.length
120 return size.to_i + @max_length * settings_count
123 def format_encoded_name(name)
124 encoded = @encoded[name]
125 raise "Name #{name} was not encoded" if encoded == nil
126 return encoded.to_carr
130 def split_words(name)
131 if @non_split.include?(name)
134 return name.split('_')
139 @names.each do |name|
140 split_words(name).each do |word|
144 # Sort by usage, then alphabetically
145 @words_by_usage = @words.keys().sort do |x, y|
158 @names.each do |name|
160 split_words(name).each do |word|
161 pos = @words_by_usage.find_index(word)
162 raise "Word #{word} not found in words array" if pos == nil
163 # Zero indicates end of words, first word in
164 # the array starts at 1 ([0] is NULL).
166 if uses_byte_indexing
172 if buf.length > @max_length
173 # TODO: print in verbose mode
174 # fmt.Printf("encoding %q took %d bytes (>%d), adding it as single word\n", v, len(data), e.MaxEncodedLength)
179 while buf.length < @max_length
190 def initialize(values, constants)
194 valuesHash = Hash.new(0)
201 # Sorted by usage, most used first
202 @values = valuesHash.keys().sort do |x, y|
212 @constants = constants
219 [8, 16, 32].each do |x|
220 if @min >= -(2**(x-1))
224 raise "cannot represent minimum value #{@min} with int32_t"
228 [8, 16, 32].each do |x|
233 raise "cannot represent maximum value #{@max} with uint32_t"
237 bits = Math.log2(@values.length).ceil
238 bytes = (bits / 8.0).ceil.to_i
240 raise "too many bytes required for value index: #{bytes}"
245 def encode_values(min, max)
247 encode_value(buf, min)
248 encode_value(buf, max)
252 def resolve_value(val)
254 if !v.is_number_kind?
257 raise "Could not resolve constant #{val}"
264 def encode_value(buf, val)
265 v = resolve_value(val)
266 pos = @values.find_index(v)
268 raise "Could not encode value not in array #{v}"
274 OFF_ON_TABLE = Hash["name" => "off_on", "values" => ["OFF", "ON"]]
277 def initialize(src_root, settings_file, output_dir, use_host_gcc)
279 @settings_file = settings_file
280 @output_dir = output_dir || File.dirname(settings_file)
282 @compiler = Compiler.new(use_host_gcc)
287 @used_tables = Set.new
288 @enabled_tables = Set.new
293 # Remove the old files first, if any
294 [header_file, impl_file].each do |file|
302 check_member_default_values_presence
304 resolv_min_max_and_default_values_if_possible
305 initialize_name_encoder
306 initialize_value_encoder
307 validate_default_values
309 write_header_file(header_file)
310 write_impl_file(impl_file)
313 def write_json(jsonFile)
315 sanitize_fields(true)
319 foreach_member do |group, member|
320 name = member["name"]
322 "type" => member["type"],
324 table = member["table"]
326 s["table"] = @tables[table]
331 File.open(jsonFile, "w") do |f|
332 f.write(JSON.pretty_generate(settings))
337 puts "#{@count} settings"
338 puts "words table has #{@name_encoder.words.length} words"
339 word_idx = @name_encoder.uses_byte_indexing ? "byte" : "uvarint"
340 puts "name encoder uses #{word_idx} word indexing"
341 puts "each setting name uses #{@name_encoder.max_length} bytes"
342 puts "#{@name_encoder.estimated_size(@count)} bytes estimated for setting name storage"
343 values_size = @value_encoder.values.length * 4
344 puts "min/max value storage uses #{values_size} bytes"
345 value_idx_size = @value_encoder.index_bytes * 2
346 value_idx_total = value_idx_size * @count
347 puts "value indexing uses #{value_idx_size} per setting, #{value_idx_total} bytes total"
348 puts "#{value_idx_size+value_idx_total} bytes estimated for value+indexes storage"
351 buf << "#include \"fc/settings.h\"\n"
352 buf << "char (*dummy)[sizeof(setting_t)] = 1;\n"
353 stderr = compile_test_file(buf)
354 puts "sizeof(setting_t) = #{/char \(\*\)\[(\d+)\]/.match(stderr)[1]}"
360 @data = YAML.load_file(@settings_file)
368 File.join(@output_dir, "settings_generated.h")
372 File.join(@output_dir, "settings_generated.c")
375 def write_file_header(buf)
376 buf << "// This file has been automatically generated by utils/settings.rb\n"
377 buf << "// Don't make any modifications to it. They will be lost.\n\n"
380 def write_header_file(file)
382 write_file_header(buf)
383 buf << "#pragma once\n"
384 # Write setting_t size constants
385 buf << "#define SETTING_MAX_NAME_LENGTH #{@max_name_length+1}\n" # +1 for the terminating '\0'
386 buf << "#define SETTING_MAX_WORD_LENGTH #{@name_encoder.max_word_length+1}\n" # +1 for the terminating '\0'
387 buf << "#define SETTING_ENCODED_NAME_MAX_BYTES #{@name_encoder.max_length}\n"
388 if @name_encoder.uses_byte_indexing
389 buf << "#define SETTING_ENCODED_NAME_USES_BYTE_INDEXING\n"
391 buf << "#define SETTINGS_WORDS_BITS_PER_CHAR #{SETTINGS_WORDS_BITS_PER_CHAR}\n"
392 buf << "#define SETTINGS_TABLE_COUNT #{@count}\n"
393 offset_type = "uint16_t"
394 if can_use_byte_offsetof
395 offset_type = "uint8_t"
397 buf << "typedef #{offset_type} setting_offset_t;\n"
399 foreach_enabled_group do |group|
402 buf << "#define SETTINGS_PGN_COUNT #{pgn_count}\n"
403 # Write type definitions and constants for min/max values
404 buf << "typedef #{@value_encoder.min_type} setting_min_t;\n"
405 buf << "typedef #{@value_encoder.max_type} setting_max_t;\n"
406 buf << "#define SETTING_MIN_MAX_INDEX_BYTES #{@value_encoder.index_bytes*2}\n"
407 # Write lookup table constants
408 table_names = ordered_table_names()
410 table_names.each do |name|
411 buf << "\t#{table_constant_name(name)},\n"
413 buf << "\tLOOKUP_TABLE_COUNT,\n"
415 # Write table pointers
416 table_names.each do |name|
417 buf << "extern const char * const #{table_variable_name(name)}[];\n"
420 # Write setting constants from settings file
421 @constants.each do |name, value|
422 buf << "#define SETTING_CONSTANT_#{name.upcase} #{value.inspect}\n"
425 # Write #define'd constants for referencing each setting
427 foreach_enabled_member do |group, member|
428 name = member["name"]
429 type = member["type"]
430 default_value = member["default_value"]
433 when %i[ zero target ].include?(default_value)
436 when member.has_key?("table")
437 table_name = member["table"]
438 table_values = @tables[table_name]["values"]
439 if table_name == 'off_on' and [false, true].include? default_value
440 default_value = { false => '0', true => '1' }[default_value]
442 default_value = table_values.index default_value
445 when type == "string"
446 default_value = "{ #{[*default_value.bytes, 0] * ', '} }"
448 when default_value.is_a?(Float)
449 default_value = default_value.to_s + ?f
453 min, max = resolve_range(member)
454 setting_name = "SETTING_#{name.upcase}"
455 buf << "#define #{setting_name}_DEFAULT #{default_value}\n" unless default_value.nil?
456 buf << "#define #{setting_name} #{ii}\n"
457 buf << "#define #{setting_name}_MIN #{min}\n"
458 buf << "#define #{setting_name}_MAX #{max}\n"
462 File.open(file, 'w') {|file| file.write(buf.string)}
465 def write_impl_file(file)
467 write_file_header(buf)
469 buf << "#include \"#{h}\"\n"
471 add_header.call("platform.h")
472 add_header.call("config/parameter_group_ids.h")
473 add_header.call("fc/settings.h")
475 foreach_enabled_group do |group|
476 (group["headers"] || []).each do |h|
481 # When this file is compiled in unit tests, some of the tables
482 # are not used and generate warnings, causing the test to fail
483 # with -Werror. Silence them
485 buf << "#pragma GCC diagnostic ignored \"-Wunused-const-variable\"\n"
490 foreach_enabled_group do |group|
492 group["members"].each do |member|
493 if is_condition_enabled(member["condition"])
498 pgns << group["name"]
501 buf << "const pgn_t settingsPgn[] = {\n"
506 buf << "const uint8_t settingsPgnCounts[] = {\n"
507 pgn_steps.each do |s|
513 buf << "static const uint8_t settingNamesWords[] = {\n"
514 word_bits = SETTINGS_WORDS_BITS_PER_CHAR
515 # We need 27 symbols for a-z + null
516 rem_symbols = 2 ** word_bits - 27
520 encode_byte = lambda do |c|
522 chr = 0 # XXX: Remove this if we go for explicit lengths
523 elsif c >= 'a'.ord && c <= 'z'.ord
524 chr = 1 + (c - 'a'.ord)
525 elsif c >= 'A'.ord && c <= 'Z'.ord
526 raise "Cannot encode uppercase character #{c.ord} (#{c})"
528 idx = symbols.index(c)
531 raise "Cannot encode character #{c.ord} (#{c}), no symbols remaining"
537 chr = 1 + ('z'.ord - 'a'.ord + 1) + idx
539 if acc_bits >= (8 - word_bits)
541 remaining = 8 - acc_bits
542 acc |= chr << (remaining - word_bits)
543 buf << "0x#{acc.to_s(16)},"
544 acc = (chr << (8 - (word_bits - remaining))) & 0xff
546 # Accumulate for next byte
547 acc |= chr << (3 - acc_bits)
549 acc_bits = (acc_bits + word_bits) % 8
551 @name_encoder.words.each do |w|
553 w.each_byte {|c| encode_byte.call(c)}
555 buf << " /* #{w.inspect} */ \n"
558 buf << "\t0x#{acc.to_s(16)},"
559 if acc_bits > (8 - word_bits)
566 # Output symbol array
567 buf << "static const char wordSymbols[] = {"
568 symbols.each { |s| buf << "'#{s.chr}'," }
571 table_names = ordered_table_names()
572 table_names.each do |name|
573 buf << "const char * const #{table_variable_name(name)}[] = {\n"
575 raise "values not found for table #{name}" unless tbl.has_key? 'values'
576 tbl["values"].each do |v|
577 buf << "\t#{v.inspect},\n"
582 buf << "static const lookupTableEntry_t settingLookupTables[] = {\n"
583 table_names.each do |name|
584 vn = table_variable_name(name)
585 buf << "\t{ #{vn}, sizeof(#{vn}) / sizeof(char*) },\n"
589 # Write min/max values table
590 buf << "static const uint32_t settingMinMaxTable[] = {\n"
591 @value_encoder.values.each do |v|
596 case @value_encoder.index_bytes
598 buf << "typedef uint8_t setting_min_max_idx_t;\n"
599 buf << "#define SETTING_INDEXES_GET_MIN(val) (val->config.minmax.indexes[0])\n"
600 buf << "#define SETTING_INDEXES_GET_MAX(val) (val->config.minmax.indexes[1])\n"
602 raise "can't encode indexed values requiring #{@value_encoder.index_bytes} bytes"
605 # Write setting_t values
606 buf << "static const setting_t settingsTable[] = {\n"
609 foreach_enabled_member do |group, member|
610 if group != last_group
612 buf << "\t// #{group["name"]}\n"
615 name = member["name"]
616 buf << "\t{ #{@name_encoder.format_encoded_name(name)}, "
617 buf << "#{var_type(member["type"])} | #{value_type(group)}"
618 tbl = member["table"]
620 buf << " | MODE_LOOKUP"
621 buf << ", .config.lookup = { #{table_constant_name(tbl)} }"
623 min, max = resolve_range(member)
625 raise "Error encoding #{name}: min (#{min}) > max (#{max})"
627 enc = @value_encoder.encode_values(min, max)
628 buf << ", .config.minmax.indexes = #{enc}"
630 buf << ", (setting_offset_t)offsetof(#{group["type"]}, #{member["field"]}) },\n"
634 File.open(file, 'w') {|file| file.write(buf.string)}
639 when "uint8_t", "bool"
654 raise "unknown variable type #{typ.inspect}"
658 def value_type(group)
659 return group["value_type"] || "MASTER_VALUE"
662 def resolve_range(member)
663 min = @value_encoder.resolve_value(member["min"])
664 max = @value_encoder.resolve_value(member["max"])
668 def is_condition_enabled(cond)
669 return !cond || @true_conditions.include?(cond)
672 def foreach_enabled_member &block
673 enum = Enumerator.new do |yielder|
674 groups.each do |group|
675 if is_condition_enabled(group["condition"])
676 group["members"].each do |member|
677 if is_condition_enabled(member["condition"])
678 yielder.yield group, member
684 block_given? ? enum.each(&block) : enum
687 def foreach_enabled_group &block
688 enum = Enumerator.new do |yielder|
690 foreach_enabled_member do |group, member|
697 block_given? ? enum.each(&block) : enum
700 def foreach_member &block
701 enum = Enumerator.new do |yielder|
702 @data["groups"].each do |group|
703 group["members"].each do |member|
704 yielder.yield group, member
708 block_given? ? enum.each(&block) : enum
715 def initialize_tables
716 @data["tables"].each do |tbl|
718 if @tables.key?(name)
719 raise "Duplicate table name #{name}"
725 def initialize_constants
726 @constants = @data["constants"]
729 def ordered_table_names
730 @enabled_tables.to_a().sort()
733 def table_constant_name(name)
734 upper = name.upcase()
738 def table_variable_name(name)
742 def can_use_byte_offsetof
744 foreach_enabled_member do |group, member|
746 field = member["field"]
747 buf << "static_assert(offsetof(#{typ}, #{field}) < 255, \"#{typ}.#{field} is too big\");\n"
749 stderr = compile_test_file(buf)
750 return !stderr.include?("static assertion failed")
754 # Use a temporary dir reachable by relative path
755 # since g++ in cygwin fails to open files
756 # with absolute paths
757 tmp = File.join(@output_dir, "tmp")
758 FileUtils.mkdir_p(tmp) unless File.directory?(tmp)
760 if File.directory?(tmp)
761 FileUtils.remove_dir(tmp)
766 def compile_test_file(prog)
768 # cstddef for offsetof()
769 headers = ["platform.h", "cstddef"]
770 @data["groups"].each do |group|
771 gh = group["headers"]
778 buf << "#include \"#{h}\"\n"
784 file = File.join(dir, "test.cpp")
785 File.open(file, 'w') {|file| file.write(buf.string)}
786 dputs "Compiling #{buf.string}"
787 stdout, stderr = @compiler.run(file, File.join(dir, "test"), '-c', noerror: true)
788 dputs "Output: #{stderr}"
796 add_condition = -> (c) {
797 if c && !conditions.include?(c)
801 c.split('').each do |ch|
803 if !ch.match(/^[a-zA-Z0-9_]$/)
809 if ch.match(/^[a-zA-Z_]$/)
820 buf << "#pragma message(#{c.inspect})\n"
825 foreach_member do |group, member|
826 add_condition.call(group["condition"])
827 add_condition.call(member["condition"])
830 stderr = compile_test_file(buf)
831 @true_conditions = Set.new
832 stderr.scan(/#pragma message\(\"(.*)\"\)/).each do |m|
833 @true_conditions << m[0]
837 def sanitize_fields(all=false)
838 pending_types = Hash.new
841 block = Proc.new do |group, member|
843 raise "Missing group name"
847 raise "Missing member name in group #{group["name"]}"
850 table = member["table"]
853 raise "Member #{member["name"]} references non-existing table #{table}"
856 @used_tables << table
860 member["field"] = member["name"]
866 member["table"] = OFF_ON_TABLE["name"]
870 all ? foreach_member(&block) : foreach_enabled_member(&block)
873 @tables[OFF_ON_TABLE["name"]] = OFF_ON_TABLE
874 @used_tables << OFF_ON_TABLE["name"]
878 foreach_enabled_member do |group, member|
880 @max_name_length = [@max_name_length, member["name"].length].max
882 @enabled_tables << member["table"]
887 def validate_default_values
888 foreach_enabled_member do |_, member|
889 name = member["name"]
890 type = member["type"]
891 min = member["min"] || 0
893 default_value = member["default_value"]
895 next if %i[ zero target ].include? default_value
899 raise "Member #{name} has an invalid default value" unless [ false, true ].include? default_value
901 when member.has_key?("table")
902 table_name = member["table"]
903 table_values = @tables[table_name]["values"]
904 raise "Member #{name} has an invalid default value" unless table_values.include? default_value
906 when type =~ /\A(?<unsigned>u?)int(?<bitsize>8|16|32|64)_t\Z/
907 unsigned = !$~[:unsigned].empty?
908 bitsize = $~[:bitsize].to_i
909 type_range = unsigned ? 0..(2**bitsize-1) : (-2**(bitsize-1)+1)..(2**(bitsize-1)-1)
910 min = type_range.min if min.to_s =~ /\AU?INT\d+_MIN\Z/
911 max = type_range.max if max.to_s =~ /\AU?INT\d+_MAX\Z/
912 raise "Member #{name} default value has an invalid type, integer or symbol expected" unless default_value.is_a? Integer or default_value.is_a? Symbol
913 raise "Member #{name} default value is outside type's storage range, min #{type_range.min}, max #{type_range.max}" unless default_value.is_a? Symbol or type_range === default_value
914 raise "Numeric member #{name} doesn't have maximum value defined" unless member.has_key? 'max'
915 raise "Member #{name} default value is outside of the allowed range" if default_value.is_a? Numeric and min.is_a? Numeric and max.is_a? Numeric and not (min..max) === default_value
918 raise "Member #{name} default value has an invalid type, numeric or symbol expected" unless default_value.is_a? Numeric or default_value.is_a? Symbol
919 raise "Numeric member #{name} doesn't have maximum value defined" unless member.has_key? 'max'
920 raise "Member #{name} default value is outside of the allowed range" if default_value.is_a? Numeric and min.is_a? Numeric and max.is_a? Numeric and not (min..max) === default_value
922 when type == "string"
923 max = member["max"].to_i
924 raise "Member #{name} default value has an invalid type, string expected" unless default_value.is_a? String
925 raise "Member #{name} default value is too long (max #{max} chars)" if default_value.bytesize > max
928 raise "Unexpected type for member #{name}: #{type.inspect}"
933 def scan_types(stderr)
936 stderr.scan(/var_(\d+).*?['’], which is of non-class type ['‘](.*)['’]/).each do |m|
937 member_idx = m[0].to_i
939 types[member_idx] = type
942 stderr.scan(/member reference base type '(.*?)'.*?is not a structure or union.*? var_(\d+)/m).each do |m|
943 member_idx = m[1].to_i
945 types[member_idx] = type
950 def resolve_all_types()
953 foreach_enabled_member do |group, member|
955 pending[member] = group
964 resolve_types(pending)
968 def resolve_types(pending)
970 prog << "int main() {\n"
973 pending.each do |member, group|
979 prog << "#{gt} #{var}; #{var}.#{mf}.__type_detect_;\n"
981 prog << "return 0;\n"
983 stderr = compile_test_file(prog)
984 types = scan_types(stderr)
986 raise "No types resolved from #{stderr}"
988 types.each do |idx, type|
989 member = members[idx]
993 when /^int8_t/ # {aka signed char}"
995 when /^uint8_t/ # {aka unsigned char}"
997 when /^int16_t/ # {aka short int}"
999 when /^uint16_t/ # {aka short unsigned int}"
1001 when /^uint32_t/ # {aka long unsigned int}"
1005 when /^char\s*\[(\d+)\]/
1006 # Substract 1 to show the maximum string size without the null terminator
1007 member["max"] = $1.to_i - 1;
1010 raise "Unknown type #{type} when resolving type for setting #{member["name"]}"
1012 dputs "#{member["name"]} type is #{typ}"
1013 member["type"] = typ
1017 def initialize_name_encoder
1019 foreach_enabled_member do |group, member|
1020 names << member["name"]
1024 enc = NameEncoder.new(names, v)
1025 if best == nil || best.estimated_size(@count) > enc.estimated_size(@count)
1029 dputs "Using name encoder with max_length = #{best.max_length}"
1030 @name_encoder = best
1033 def initialize_value_encoder
1036 add_value = -> (v) {
1038 if v.is_number_kind? || (v.class == String && v.is_number?)
1044 foreach_enabled_member do |group, member|
1045 add_value.call(member["min"])
1046 add_value.call(member["max"])
1048 constantValues = resolve_constants(constants)
1049 # Count values used by constants
1050 constants.each do |c|
1051 values << constantValues[c]
1053 @value_encoder = ValueEncoder.new(values, constantValues)
1056 def check_member_default_values_presence
1057 missing_default_value_names = foreach_member.inject([]) { |names, (_, member)| member.has_key?("default_value") ? names : names << member["name"] }
1058 raise "Missing default value for #{missing_default_value_names.count} member#{"s" unless missing_default_value_names.one?}: #{missing_default_value_names * ", "}" unless missing_default_value_names.empty?
1061 def resolv_min_max_and_default_values_if_possible
1062 foreach_member do |_, member|
1063 %w[ min max default_value ].each do |value_type|
1064 member_value = member[value_type]
1065 if member_value.is_a? String
1066 constant_value = @constants[member_value]
1067 member[value_type] = constant_value unless constant_value.nil?
1073 def resolve_constants(constants)
1074 return nil unless constants.length > 0
1076 constants.each do |c|
1079 dputs "#{constants.length} constants to resolve"
1080 # Since we're relying on errors rather than
1081 # warnings to find these constants, the compiler
1082 # might reach the maximum number of errors and stop
1083 # compilation, so we might need multiple passes.
1084 gcc_re = /required from ['‘]class expr_(.*?)<(.*?)>['’]/ # gcc 6-9
1085 clang_re = / template class 'expr_(.*?)<(.*?)>'/ # clang
1086 res = [gcc_re, clang_re]
1090 buf << "template <int64_t V> class Fail {\n"
1091 # Include V in the static_assert so it's shown
1092 # in the error condition.
1093 buf << "static_assert(V == 42 && 0 == 1, \"FAIL\");\n"
1095 buf << "Fail() {};\n"
1096 buf << "int64_t v = V;\n"
1101 buf << "template <int64_t V> class #{cls}: public Fail<V> {};\n"
1102 buf << "#{cls}<#{c}> var_#{ii};\n"
1105 stderr = compile_test_file(buf)
1108 if matches.length == 0
1109 matches = stderr.scan(re)
1112 if matches.length == 0
1114 raise "No more matches looking for constants"
1119 # gcc 6.3 includes an ul or ll prefix after the
1120 # constant expansion, while gcc 7.1 does not
1125 dputs "Constant #{c} resolved to #{nv}"
1133 puts "Usage: ruby #{__FILE__} <source_dir> <settings_file> [--use_host_gcc] [--json <json_file>]"
1138 verbose = ENV["V"] == "1"
1141 settings_file = ARGV[1]
1142 if src_root.nil? || settings_file.nil?
1148 opts = GetoptLong.new(
1149 [ "--output-dir", "-o", GetoptLong::REQUIRED_ARGUMENT ],
1150 [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
1151 [ "--json", "-j", GetoptLong::REQUIRED_ARGUMENT ],
1152 [ "--use_host_gcc", "-g", GetoptLong::NO_ARGUMENT ]
1159 opts.each do |opt, arg|
1168 when "--use_host_gcc"
1173 gen = Generator.new(src_root, settings_file, output_dir, use_host_gcc)
1176 gen.write_json(jsonFile)