ldgen.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python
  2. #
  3. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  4. # SPDX-License-Identifier: Apache-2.0
  5. #
  6. import argparse
  7. import errno
  8. import json
  9. import os
  10. import subprocess
  11. import sys
  12. import tempfile
  13. from io import StringIO
  14. from ldgen.entity import EntityDB
  15. from ldgen.fragments import parse_fragment_file
  16. from ldgen.generation import Generation
  17. from ldgen.ldgen_common import LdGenFailure
  18. from ldgen.linker_script import LinkerScript
  19. from ldgen.sdkconfig import SDKConfig
  20. from pyparsing import ParseException, ParseFatalException
  21. def _update_environment(args):
  22. env = [(name, value) for (name,value) in (e.split('=',1) for e in args.env)]
  23. for name, value in env:
  24. value = ' '.join(value.split())
  25. os.environ[name] = value
  26. if args.env_file is not None:
  27. env = json.load(args.env_file)
  28. os.environ.update(env)
  29. def main():
  30. argparser = argparse.ArgumentParser(description='ESP-IDF linker script generator')
  31. argparser.add_argument(
  32. '--input', '-i',
  33. help='Linker template file',
  34. type=argparse.FileType('r'))
  35. fragments_group = argparser.add_mutually_exclusive_group()
  36. fragments_group.add_argument(
  37. '--fragments', '-f',
  38. type=argparse.FileType('r'),
  39. help='Input fragment files',
  40. nargs='+'
  41. )
  42. fragments_group.add_argument(
  43. '--fragments-list',
  44. help='Input fragment files as a semicolon-separated list',
  45. type=str
  46. )
  47. argparser.add_argument(
  48. '--libraries-file',
  49. type=argparse.FileType('r'),
  50. help='File that contains the list of libraries in the build')
  51. argparser.add_argument(
  52. '--output', '-o',
  53. help='Output linker script',
  54. type=str)
  55. argparser.add_argument(
  56. '--config', '-c',
  57. help='Project configuration')
  58. argparser.add_argument(
  59. '--kconfig', '-k',
  60. help='IDF Kconfig file')
  61. argparser.add_argument(
  62. '--check-mapping',
  63. help='Perform a check if a mapping (archive, obj, symbol) exists',
  64. action='store_true'
  65. )
  66. argparser.add_argument(
  67. '--check-mapping-exceptions',
  68. help='Mappings exempted from check',
  69. type=argparse.FileType('r')
  70. )
  71. argparser.add_argument(
  72. '--env', '-e',
  73. action='append', default=[],
  74. help='Environment to set when evaluating the config file', metavar='NAME=VAL')
  75. argparser.add_argument('--env-file', type=argparse.FileType('r'),
  76. help='Optional file to load environment variables from. Contents '
  77. 'should be a JSON object where each key/value pair is a variable.')
  78. argparser.add_argument(
  79. '--objdump',
  80. help='Path to toolchain objdump')
  81. args = argparser.parse_args()
  82. input_file = args.input
  83. libraries_file = args.libraries_file
  84. config_file = args.config
  85. output_path = args.output
  86. kconfig_file = args.kconfig
  87. objdump = args.objdump
  88. fragment_files = []
  89. if args.fragments_list:
  90. fragment_files = args.fragments_list.split(';')
  91. elif args.fragments:
  92. fragment_files = args.fragments
  93. check_mapping = args.check_mapping
  94. if args.check_mapping_exceptions:
  95. check_mapping_exceptions = [line.strip() for line in args.check_mapping_exceptions]
  96. else:
  97. check_mapping_exceptions = None
  98. try:
  99. sections_infos = EntityDB()
  100. for library in libraries_file:
  101. library = library.strip()
  102. if library:
  103. new_env = os.environ.copy()
  104. new_env['LC_ALL'] = 'C'
  105. dump = StringIO(subprocess.check_output([objdump, '-h', library], env=new_env).decode())
  106. dump.name = library
  107. sections_infos.add_sections_info(dump)
  108. generation_model = Generation(check_mapping, check_mapping_exceptions)
  109. _update_environment(args) # assign args.env and args.env_file to os.environ
  110. sdkconfig = SDKConfig(kconfig_file, config_file)
  111. for fragment_file in fragment_files:
  112. try:
  113. fragment_file = parse_fragment_file(fragment_file, sdkconfig)
  114. except (ParseException, ParseFatalException) as e:
  115. # ParseException is raised on incorrect grammar
  116. # ParseFatalException is raised on correct grammar, but inconsistent contents (ex. duplicate
  117. # keys, key unsupported by fragment, unexpected number of values, etc.)
  118. raise LdGenFailure('failed to parse %s\n%s' % (fragment_file, str(e)))
  119. generation_model.add_fragments_from_file(fragment_file)
  120. mapping_rules = generation_model.generate(sections_infos)
  121. script_model = LinkerScript(input_file)
  122. script_model.fill(mapping_rules)
  123. with tempfile.TemporaryFile('w+') as output:
  124. script_model.write(output)
  125. output.seek(0)
  126. if not os.path.exists(os.path.dirname(output_path)):
  127. try:
  128. os.makedirs(os.path.dirname(output_path))
  129. except OSError as exc:
  130. if exc.errno != errno.EEXIST:
  131. raise
  132. with open(output_path, 'w') as f: # only create output file after generation has suceeded
  133. f.write(output.read())
  134. except LdGenFailure as e:
  135. print('linker script generation failed for %s\nERROR: %s' % (input_file.name, e))
  136. sys.exit(1)
  137. if __name__ == '__main__':
  138. main()