objcopy.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import argparse
  2. import sys, os
  3. import base64
  4. import hashlib
  5. import tempfile
  6. import shutil
  7. import subprocess
  8. from collections import namedtuple
  9. def call(cmd, cwd=None, env=None):
  10. return subprocess.check_output(cmd, stdin=None, stderr=subprocess.STDOUT, cwd=cwd, env=env)
  11. class LLVMResourceInserter:
  12. def __init__(self, args, obj_output):
  13. self.cxx = args.compiler
  14. self.objcopy = args.objcopy
  15. self.rescompiler = args.rescompiler
  16. self.compressor = args.compressor
  17. self.obj_out = obj_output
  18. has_target = args.target or args.target != '__unknown_target__'
  19. self.target_flags = [f"--target={args.target}"] if has_target else []
  20. seed = os.path.basename(obj_output)
  21. self.mangler = lambda key: 'R' + hashlib.sha256((seed + key).encode()).hexdigest()
  22. self.tmpdir = tempfile.mkdtemp()
  23. def __enter__(self):
  24. return self
  25. def __exit__(self, *_):
  26. shutil.rmtree(self.tmpdir)
  27. PackedArgs = namedtuple("PackedArgs", ["infile_path", "comressed_file_path", "symbol_name"])
  28. def _insert_files(self, infos: list[PackedArgs], output_obj: str) -> None:
  29. if len(infos) == 0:
  30. return
  31. # Compress resources
  32. cmd = [self.compressor, "--compress-only"]
  33. for inserted_file, compressed_file, _ in infos:
  34. cmd.extend([inserted_file, compressed_file])
  35. call(cmd)
  36. # Put resources into .o file
  37. infos = [(zipped_file, os.path.getsize(zipped_file), symbol_name) for (_, zipped_file, symbol_name) in infos]
  38. cmd = [self.objcopy]
  39. # NOTE: objcopy does not distinguish the order of arguments.
  40. for fname, fsize, sym in infos:
  41. section_name = f'.rodata.{sym}'
  42. cmd.extend(
  43. [
  44. f'--add-section={section_name}={fname}',
  45. f'--set-section-flags={section_name}=readonly',
  46. f'--add-symbol={sym}={section_name}:0,global',
  47. f'--add-symbol={sym}_end={section_name}:{fsize},global',
  48. ]
  49. )
  50. cmd.extend([output_obj])
  51. call(cmd)
  52. @staticmethod
  53. def flat_merge_cpp(outs, output):
  54. if len(outs) == 1:
  55. shutil.move(outs[0], output)
  56. return
  57. with open(output, 'w') as fout:
  58. for fname in outs:
  59. with open(fname, 'r') as fin:
  60. shutil.copyfileobj(fin, fout)
  61. return
  62. def insert_resources(self, kv_files, kv_strings):
  63. kv_files = list(kv_files)
  64. # Generate resource registration cpp code & compile it
  65. with tempfile.NamedTemporaryFile(suffix='.cc') as dummy_src:
  66. cmd = [self.rescompiler, dummy_src.name, '--use-sections']
  67. for path, key in kv_files + list(('-', k) for k in kv_strings):
  68. if path != '-':
  69. path = self.mangler(key)
  70. cmd.extend([path, key])
  71. call(cmd)
  72. # Compile
  73. call([self.cxx, dummy_src.name, *self.target_flags, '-c', '-o', self.obj_out])
  74. # Put files
  75. infos = [[]]
  76. estimated_cmd_len = 0
  77. LIMIT = 6000
  78. for idx, (path, key) in enumerate(kv_files):
  79. packed_args = (path, os.path.join(self.tmpdir, f'{idx}.zstd'), self.mangler(key))
  80. infos[-1].append(packed_args)
  81. estimated_cmd_len += len(path)
  82. if estimated_cmd_len > LIMIT:
  83. infos.append([])
  84. estimated_cmd_len = 0
  85. for packed_args in infos:
  86. self._insert_files(packed_args, self.obj_out)
  87. return self.obj_out
  88. def parse_args():
  89. parser = argparse.ArgumentParser()
  90. parser.add_argument('--compiler', required=True)
  91. parser.add_argument('--objcopy', required=True)
  92. parser.add_argument('--compressor', required=True)
  93. parser.add_argument('--rescompiler', required=True)
  94. parser.add_argument('--output_obj', required=True)
  95. parser.add_argument('--inputs', nargs='+', required=False, default=[])
  96. parser.add_argument('--keys', nargs='+', required=False, default=[])
  97. parser.add_argument('--kvs', nargs='+', required=False, default=[])
  98. parser.add_argument('--target', required=True)
  99. args = parser.parse_args()
  100. # Decode hex to original string
  101. args.keys = list(base64.b64decode(it).decode("utf-8") for it in args.keys)
  102. return args, args.inputs, args.keys
  103. def main():
  104. args, inputs, keys = parse_args()
  105. with LLVMResourceInserter(args, args.output_obj) as inserter:
  106. inserter.insert_resources(zip(inputs, keys), args.kvs)
  107. return
  108. if __name__ == '__main__':
  109. main()