import argparse import sys, os import base64 import hashlib import tempfile import shutil import subprocess from collections import namedtuple def call(cmd, cwd=None, env=None): return subprocess.check_output(cmd, stdin=None, stderr=subprocess.STDOUT, cwd=cwd, env=env) class LLVMResourceInserter: def __init__(self, args, obj_output): self.cxx = args.compiler self.objcopy = args.objcopy self.rescompiler = args.rescompiler self.compressor = args.compressor self.obj_out = obj_output has_target = args.target or args.target != '__unknown_target__' self.target_flags = [f"--target={args.target}"] if has_target else [] seed = os.path.basename(obj_output) self.mangler = lambda key: 'R' + hashlib.sha256((seed + key).encode()).hexdigest() self.tmpdir = tempfile.mkdtemp() def __enter__(self): return self def __exit__(self, *_): shutil.rmtree(self.tmpdir) PackedArgs = namedtuple("PackedArgs", ["infile_path", "comressed_file_path", "symbol_name"]) def _insert_files(self, infos: list[PackedArgs], output_obj: str) -> None: if len(infos) == 0: return # Compress resources cmd = [self.compressor, "--compress-only"] for inserted_file, compressed_file, _ in infos: cmd.extend([inserted_file, compressed_file]) call(cmd) # Put resources into .o file infos = [(zipped_file, os.path.getsize(zipped_file), symbol_name) for (_, zipped_file, symbol_name) in infos] cmd = [self.objcopy] # NOTE: objcopy does not distinguish the order of arguments. for fname, fsize, sym in infos: section_name = f'.rodata.{sym}' cmd.extend( [ f'--add-section={section_name}={fname}', f'--set-section-flags={section_name}=readonly', f'--add-symbol={sym}={section_name}:0,global', f'--add-symbol={sym}_end={section_name}:{fsize},global', ] ) cmd.extend([output_obj]) call(cmd) @staticmethod def flat_merge_cpp(outs, output): if len(outs) == 1: shutil.move(outs[0], output) return with open(output, 'w') as fout: for fname in outs: with open(fname, 'r') as fin: shutil.copyfileobj(fin, fout) return def insert_resources(self, kv_files, kv_strings): kv_files = list(kv_files) # Generate resource registration cpp code & compile it with tempfile.NamedTemporaryFile(suffix='.cc') as dummy_src: cmd = [self.rescompiler, dummy_src.name, '--use-sections'] for path, key in kv_files + list(('-', k) for k in kv_strings): if path != '-': path = self.mangler(key) cmd.extend([path, key]) call(cmd) # Compile call([self.cxx, dummy_src.name, *self.target_flags, '-c', '-o', self.obj_out]) # Put files infos = [[]] estimated_cmd_len = 0 LIMIT = 6000 for idx, (path, key) in enumerate(kv_files): packed_args = (path, os.path.join(self.tmpdir, f'{idx}.zstd'), self.mangler(key)) infos[-1].append(packed_args) estimated_cmd_len += len(path) if estimated_cmd_len > LIMIT: infos.append([]) estimated_cmd_len = 0 for packed_args in infos: self._insert_files(packed_args, self.obj_out) return self.obj_out def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--compiler', required=True) parser.add_argument('--objcopy', required=True) parser.add_argument('--compressor', required=True) parser.add_argument('--rescompiler', required=True) parser.add_argument('--output_obj', required=True) parser.add_argument('--inputs', nargs='+', required=False, default=[]) parser.add_argument('--keys', nargs='+', required=False, default=[]) parser.add_argument('--kvs', nargs='+', required=False, default=[]) parser.add_argument('--target', required=True) args = parser.parse_args() # Decode hex to original string args.keys = list(base64.b64decode(it).decode("utf-8") for it in args.keys) return args, args.inputs, args.keys def main(): args, inputs, keys = parse_args() with LLVMResourceInserter(args, args.output_obj) as inserter: inserter.insert_resources(zip(inputs, keys), args.kvs) return if __name__ == '__main__': main()