cpp_proto_wrapper.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import sys
  2. import os
  3. import subprocess
  4. import re
  5. import argparse
  6. import shutil
  7. FROM_RE = re.compile(r"((?:struct|class)\s+\S+\s+)final\s*:")
  8. TO_RE = r"\1:"
  9. def parse_args() -> argparse.Namespace:
  10. parser = argparse.ArgumentParser()
  11. parser.add_argument('--outputs', nargs='+', required=True)
  12. parser.add_argument('subcommand', nargs='+')
  13. return parser.parse_args()
  14. def patch_proto_file(text: str) -> tuple[str, int]:
  15. num_patches = 0
  16. patches = [
  17. (re.compile(r"((?:struct|class)\s+\S+\s+)final\s*:"), r"\1:"),
  18. (re.compile(r'(#include.*?)(\.proto\.h)"'), r'\1.pb.h"')
  19. ]
  20. for from_re, to_re in patches:
  21. text, n = re.subn(from_re, to_re, text)
  22. num_patches += n
  23. return text, num_patches
  24. def strip_file_ext(path: str) -> str:
  25. dirname, filename = os.path.dirname(path), os.path.basename(path)
  26. filename = filename.split('.')[0]
  27. return os.path.join(dirname, filename)
  28. def change_file_ext(path: str, change_map: dict[str, str]) -> str:
  29. dirname, filename = os.path.dirname(path), os.path.basename(path)
  30. filename = filename.split('.')
  31. filename, ext = filename[0], '.' + '.'.join(filename[1:])
  32. if not change_map.get(ext):
  33. return
  34. new_ext = change_map[ext]
  35. old = os.path.join(dirname, filename + ext)
  36. new = os.path.join(dirname, filename + new_ext)
  37. shutil.move(old, new)
  38. return new
  39. def main(namespace: argparse.Namespace) -> int:
  40. lite_protobuf_headers = any(out.endswith('.deps.pb.h') for out in namespace.outputs)
  41. ev_proto = any(out.endswith('.ev.pb.h') for out in namespace.outputs)
  42. if ev_proto:
  43. pattern = re.compile(r'proto_h=true:')
  44. disable_lite_headers = lambda s: re.sub(pattern, '', s)
  45. namespace.subcommand = [disable_lite_headers(argv) for argv in namespace.subcommand]
  46. try:
  47. env = os.environ.copy()
  48. if lite_protobuf_headers:
  49. env['PROTOC_PLUGINS_LITE_HEADERS']='1'
  50. subprocess.check_output(namespace.subcommand, stdin=None, stderr=subprocess.STDOUT, env=env)
  51. except subprocess.CalledProcessError as e:
  52. sys.stderr.write(
  53. '{} returned non-zero exit code {}.\n{}\n'.format(' '.join(e.cmd), e.returncode, e.output.decode('utf-8', errors='ignore'))
  54. )
  55. return e.returncode
  56. if lite_protobuf_headers:
  57. paths = [strip_file_ext(out) for out in namespace.outputs if out.endswith('.deps.pb.h')]
  58. proto_h_files = [out + '.proto.h' for out in paths]
  59. pb_h_files = [out + '.pb.h' for out in paths]
  60. change_map = {
  61. '.proto.h': '.pb.h',
  62. '.pb.h': '.deps.pb.h',
  63. }
  64. [change_file_ext(out, change_map) for out in pb_h_files]
  65. [change_file_ext(out, change_map) for out in proto_h_files]
  66. for output in namespace.outputs:
  67. with open(output, 'rt', encoding="utf-8") as f:
  68. patched_text, num_patches = patch_proto_file(f.read())
  69. if num_patches:
  70. with open(output, 'wt', encoding="utf-8") as f:
  71. f.write(patched_text)
  72. return 0
  73. if __name__ == '__main__':
  74. sys.exit(main(parse_args()))