def test_equivalent_conditional_fragment(self): test_fragment1 = self.create_fragment_file(u""" if A = y: [sections:test1] entries: value_1 else: [sections:test2] entries: value_2 """) fragment_file1 = parse_fragment_file(test_fragment1, self.sdkconfig) self.assertEqual(fragment_file1.fragments[0].name, 'test1') self.assertEqual(fragment_file1.fragments[0].entries, {'value_1'}) test_fragment2 = self.create_fragment_file(u""" [sections:test1] entries: if A = y: value_1 else: value_2 """) fragment_file2 = parse_fragment_file(test_fragment2, self.sdkconfig) self.assertEqual(fragment_file2.fragments[0].name, 'test1') self.assertEqual(fragment_file2.fragments[0].entries, {'value_1'})
def test_unsupported_key(self): test_fragment = self.create_fragment_file(u""" [sections:test] key_1: value_a """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_improper_grammar(self): test_fragment = self.create_fragment_file(u""" [scheme:test] entries: sections1, target1 # improper separator """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_settings_unmatch_indent(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 value_2 # first element dictates indent value_3 """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_flags_entries_multiple_flags_and_entries(self): # Not an error, generation step handles this, since # it that step has a more complete information # about all mappings. This can happen across multiple # mapping fragments. test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name) obj1 (default); text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [ Flag('text', 'flash_text', [ Align(4, True, False), Keep(), Surround('sym1'), Sort('name') ]), Flag('text', 'flash_text', [ Align(4, True, False), Keep(), Surround('sym1'), Sort('name') ]) ] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual)
def test_empty_entries(self): test_fragment = self.create_fragment_file(u""" [scheme:test] entries: """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [scheme:test] entries: if B = y: sections1 -> target1 """) with self.assertRaises(ParseFatalException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_flag_order(self): # Test that the order in which the flags are specified is retained test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text ALIGN(4) KEEP() SURROUND(sym1) ALIGN(8) SORT(name), rodata->flash_rodata KEEP() ALIGN(4) KEEP() SURROUND(sym1) ALIGN(8) ALIGN(4) SORT(name) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [ Flag('text', 'flash_text', [ Align(4, True, False), Keep(), Surround('sym1'), Align(8, True, False), Sort('name') ]), Flag('rodata', 'flash_rodata', [ Keep(), Align(4, True, False), Keep(), Surround('sym1'), Align(8, True, False), Align(4, True, False), Sort('name') ]) ] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual)
def test_sort_flag(self): # Test parsing combinations and orders of flags test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text SORT(name), rodata->flash_rodata SORT(alignment), data->dram0_data SORT(init_priority), bss->dram0_bss SORT(name, alignment), common->dram0_bss SORT(alignment, name), iram->iram0_text SORT(name, name), dram->dram0_data SORT(alignment, alignment) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [ Flag('text', 'flash_text', [Sort('name')]), Flag('rodata', 'flash_rodata', [Sort('alignment')]), Flag('data', 'dram0_data', [Sort('init_priority')]), Flag('bss', 'dram0_bss', [Sort('name', 'alignment')]), Flag('common', 'dram0_bss', [Sort('alignment', 'name')]), Flag('iram', 'iram0_text', [Sort('name', 'name')]), Flag('dram', 'dram0_data', [Sort('alignment', 'alignment')]) ] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual)
def test_basic(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: .section1 .section2 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].entries, {'.section1', '.section2'})
def test_archive_allowed_names(self): test_fragment = self.create_fragment_file(u""" [mapping:test] archive: libstdc++.a entries: * (default) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual('libstdc++.a', fragment_file.fragments[0].archive)
def test_archive(self): test_fragment = self.create_fragment_file(u""" [mapping:test] archive: entries: * (default) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib1.a lib2.a entries: * (default) """) with self.assertRaises(ParseFatalException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_out_of_order_conditional(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: elif B = y: value_1 else: value_2 """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [sections:test] entries: else: value_2 """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_basic(self): test_fragment = self.create_fragment_file(u""" [scheme:test] entries: sections1 -> target1 sections2 -> target2 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].entries, {('sections1', 'target1'), ('sections2', 'target2')})
def test_align_flag(self): # Test parsing combinations and orders of flags test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text ALIGN(8), rodata->flash_rodata ALIGN(8, pre), data->dram0_data ALIGN(8, pre, post), bss->dram0_bss ALIGN(8, post), common->dram0_bss ALIGN(8, pre, post) ALIGN(8) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [ Flag('text', 'flash_text', [Align(8, True, False)]), Flag('rodata', 'flash_rodata', [Align(8, True, False)]), Flag('data', 'dram0_data', [Align(8, True, True)]), Flag('bss', 'dram0_bss', [Align(8, False, True)]), Flag('common', 'dram0_bss', [Align(8, True, True), Align(8, True, False)]) ] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual) # Wrong post, pre order test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (noflash) text->iram0_text ALIGN(8, post, pre) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_setting_indent(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 value_2 value_3 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test') self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3'})
def test_duplicate_entries(self): test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: obj:symbol (noflash) obj:symbol (noflash) """) expected = {('obj', 'symbol', 'noflash')} fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(expected, fragment_file.fragments[0].entries)
def test_surround_flag(self): # Test parsing combinations and orders of flags test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text SURROUND(sym1) """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [Flag('text', 'flash_text', [Surround('sym1')])] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual)
def test_empty_entries(self): test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: if B = y: * (noflash) # if condition is false, then no 'entries' key value """) expected = set() fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(expected, fragment_file.fragments[0].entries) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_basic(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 value_2 # comments should be ignored value_3 # this is a comment as well value_a # this is the last comment """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test') self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3', 'value_a'})
def test_entries_grammar(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: _valid1 valid2. .valid3_- """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].entries, {'_valid1', 'valid2.', '.valid3_-'}) # invalid starting char test_fragment = self.create_fragment_file(u""" [sections:test] entries: 1invalid """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [sections:test] entries: -invalid """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) # + notation test_fragment = self.create_fragment_file(u""" [sections:test] entries: valid+ """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].entries, {'valid+'}) test_fragment = self.create_fragment_file(u""" [sections:test] entries: inva+lid+ """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_multiple_fragments(self): test_fragment = self.create_fragment_file(u""" [sections:test1] entries: value_1 [scheme:test2] entries: section -> target """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test1') self.assertEqual(fragment_file.fragments[0].entries, {'value_1'}) self.assertEqual(fragment_file.fragments[1].name, 'test2') self.assertEqual(fragment_file.fragments[1].entries, {('section', 'target')})
def test_keep_flag(self): # Test parsing combinations and orders of flags test_fragment = self.create_fragment_file(u""" [mapping:map] archive: libmain.a entries: obj1 (default); text->flash_text KEEP(), rodata->flash_rodata KEEP() KEEP() """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) fragment = fragment_file.fragments[0] expected = [ Flag('text', 'flash_text', [Keep()]), Flag('rodata', 'flash_rodata', [Keep(), Keep()]) ] actual = fragment.flags[('obj1', None, 'default')] self.assertEqual(expected, actual)
def test_whole_conditional_fragment(self): test_fragment = self.create_fragment_file(u""" if B = y: [sections:test1] entries: value_1 else: [sections:test2] entries: value_2 if A = y: [sections:test3] entries: value_3 if C = y: value_6 [sections:test4] entries: value_4 [sections:test5] entries: value_5 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(len(fragment_file.fragments), 4) self.assertEqual(fragment_file.fragments[0].name, 'test2') self.assertEqual(fragment_file.fragments[0].entries, {'value_2'}) self.assertEqual(fragment_file.fragments[1].name, 'test3') self.assertEqual(fragment_file.fragments[1].entries, {'value_3', 'value_6'}) self.assertEqual(fragment_file.fragments[2].name, 'test4') self.assertEqual(fragment_file.fragments[2].entries, {'value_4'}) self.assertEqual(fragment_file.fragments[3].name, 'test5') self.assertEqual(fragment_file.fragments[3].entries, {'value_5'})
def test_conditional(self): test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 if A = y: value_2 value_3 if A = n: value_4 if B = n: value_5 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test') self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3', 'value_5'}) test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 if B = y: value_2 elif C = y: value_3 elif A = y: value_4 else: value_5 value_6 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test') self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_3', 'value_6'}) test_fragment = self.create_fragment_file(u""" [sections:test] entries: value_1 if A = y: value_2 if B = y: value_3 else: value_4 if C = y: value_5 value_6 value_7 """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(fragment_file.fragments[0].name, 'test') self.assertEqual( fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_4', 'value_5', 'value_6', 'value_7'}) test_fragment = self.create_fragment_file(u""" [sections:test] entries: if A = n: value_2 """) with self.assertRaises(ParseFatalException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_invalid_grammar(self): test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] entries: * (default) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: obj: (noflash) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: obj: () """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: obj:symbol """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: (noflash) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: obj:* (noflash) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: :symbol (noflash) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig) test_fragment = self.create_fragment_file(u""" [mapping:test] archive: lib.a entries: *:symbol (noflash) """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)
def test_empty_file(self): test_fragment = self.create_fragment_file(u""" """) fragment_file = parse_fragment_file(test_fragment, self.sdkconfig) self.assertEqual(len(fragment_file.fragments), 0)
def main(): argparser = argparse.ArgumentParser( description='ESP-IDF linker script generator') argparser.add_argument('--input', '-i', help='Linker template file', type=argparse.FileType('r')) fragments_group = argparser.add_mutually_exclusive_group() fragments_group.add_argument('--fragments', '-f', type=argparse.FileType('r'), help='Input fragment files', nargs='+') fragments_group.add_argument( '--fragments-list', help='Input fragment files as a semicolon-separated list', type=str) argparser.add_argument( '--libraries-file', type=argparse.FileType('r'), help='File that contains the list of libraries in the build') argparser.add_argument('--output', '-o', help='Output linker script', type=str) argparser.add_argument('--config', '-c', help='Project configuration') argparser.add_argument('--kconfig', '-k', help='IDF Kconfig file') argparser.add_argument( '--check-mapping', help='Perform a check if a mapping (archive, obj, symbol) exists', action='store_true') argparser.add_argument('--check-mapping-exceptions', help='Mappings exempted from check', type=argparse.FileType('r')) argparser.add_argument( '--env', '-e', action='append', default=[], help='Environment to set when evaluating the config file', metavar='NAME=VAL') argparser.add_argument( '--env-file', type=argparse.FileType('r'), help='Optional file to load environment variables from. Contents ' 'should be a JSON object where each key/value pair is a variable.') argparser.add_argument('--objdump', help='Path to toolchain objdump') args = argparser.parse_args() input_file = args.input libraries_file = args.libraries_file config_file = args.config output_path = args.output kconfig_file = args.kconfig objdump = args.objdump fragment_files = [] if args.fragments_list: fragment_files = args.fragments_list.split(';') elif args.fragments: fragment_files = args.fragments check_mapping = args.check_mapping if args.check_mapping_exceptions: check_mapping_exceptions = [ line.strip() for line in args.check_mapping_exceptions ] else: check_mapping_exceptions = None try: sections_infos = EntityDB() for library in libraries_file: library = library.strip() if library: new_env = os.environ.copy() new_env['LC_ALL'] = 'C' dump = StringIO( subprocess.check_output([objdump, '-h', library], env=new_env).decode()) dump.name = library sections_infos.add_sections_info(dump) generation_model = Generation(check_mapping, check_mapping_exceptions) _update_environment( args) # assign args.env and args.env_file to os.environ sdkconfig = SDKConfig(kconfig_file, config_file) for fragment_file in fragment_files: try: fragment_file = parse_fragment_file(fragment_file, sdkconfig) except (ParseException, ParseFatalException) as e: # ParseException is raised on incorrect grammar # ParseFatalException is raised on correct grammar, but inconsistent contents (ex. duplicate # keys, key unsupported by fragment, unexpected number of values, etc.) raise LdGenFailure('failed to parse %s\n%s' % (fragment_file, str(e))) generation_model.add_fragments_from_file(fragment_file) mapping_rules = generation_model.generate(sections_infos) script_model = LinkerScript(input_file) script_model.fill(mapping_rules) with tempfile.TemporaryFile('w+') as output: script_model.write(output) output.seek(0) if not os.path.exists(os.path.dirname(output_path)): try: os.makedirs(os.path.dirname(output_path)) except OSError as exc: if exc.errno != errno.EEXIST: raise with open( output_path, 'w' ) as f: # only create output file after generation has suceeded f.write(output.read()) except LdGenFailure as e: print('linker script generation failed for %s\nERROR: %s' % (input_file.name, e)) sys.exit(1)
def test_empty_fragment(self): test_fragment = self.create_fragment_file(u""" [sections:test] """) with self.assertRaises(ParseException): parse_fragment_file(test_fragment, self.sdkconfig)