ldgen.py 5.7 KB

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