amalgamate.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import re
  2. import shutil
  3. import os
  4. import sys
  5. import time
  6. from typing import List, Dict
  7. assert os.system("python prebuild.py") == 0
  8. ROOT = 'include/pocketpy'
  9. PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h']
  10. COPYRIGHT = '''/*
  11. * Copyright (c) 2024 blueloveTH
  12. * Distributed Under The MIT License
  13. * https://github.com/pocketpy/pocketpy
  14. */
  15. '''
  16. def read_file(path):
  17. with open(path, 'rt', encoding='utf-8') as f:
  18. return f.read()
  19. def write_file(path, content):
  20. with open(path, 'wt', encoding='utf-8', newline='\n') as f:
  21. f.write(content)
  22. if os.path.exists('amalgamated'):
  23. shutil.rmtree('amalgamated')
  24. time.sleep(0.5)
  25. os.mkdir('amalgamated')
  26. class Header:
  27. path: str
  28. content: str # header source
  29. dependencies: List[str]
  30. def __init__(self, path: str):
  31. self.path = path
  32. self.dependencies = []
  33. self.content = read_file(f'{ROOT}/{path}')
  34. # process raw content and get dependencies
  35. self.content = self.content.replace('#pragma once', '')
  36. def _replace(m):
  37. path = m.group(1)
  38. if path.startswith('xmacros/'):
  39. return read_file(f'{ROOT}/{path}') + '\n'
  40. if path in PUBLIC_HEADERS:
  41. return '' # remove include
  42. if path != self.path:
  43. self.dependencies.append(path)
  44. return '' # remove include
  45. self.content = re.sub(
  46. r'#include\s+"pocketpy/(.+)"\s*',
  47. _replace,
  48. self.content
  49. )
  50. def __repr__(self):
  51. return f'Header({self.path!r}, dependencies={self.dependencies})'
  52. def text(self):
  53. return f'// {self.path}\n{self.content}\n'
  54. headers: Dict[str, Header] = {}
  55. for entry in os.listdir(ROOT):
  56. if os.path.isdir(f'{ROOT}/{entry}'):
  57. if entry == 'xmacros' or entry in PUBLIC_HEADERS:
  58. continue
  59. files = os.listdir(f'{ROOT}/{entry}')
  60. for file in sorted(files):
  61. assert file.endswith('.h')
  62. if entry in PUBLIC_HEADERS:
  63. continue
  64. headers[f'{entry}/{file}'] = Header(f'{entry}/{file}')
  65. def merge_c_files():
  66. c_files = [COPYRIGHT, '\n', '#include "pocketpy.h"', '\n']
  67. # merge internal headers
  68. internal_h = []
  69. while True:
  70. for h in headers.values():
  71. if not h.dependencies:
  72. break
  73. else:
  74. if headers:
  75. print(headers)
  76. raise RuntimeError("Circular dependencies detected")
  77. break
  78. # print(h.path)
  79. internal_h.append(h.text())
  80. del headers[h.path]
  81. for h2 in headers.values():
  82. h2.dependencies = [d for d in h2.dependencies if d != h.path]
  83. c_files.extend(internal_h)
  84. def _replace(m):
  85. path = m.group(1)
  86. if path.startswith('xmacros/'):
  87. return read_file(f'{ROOT}/{path}') + '\n'
  88. return '' # remove include
  89. for root, _, files in os.walk('src/'):
  90. for file in files:
  91. if file.endswith('.c'):
  92. path = os.path.join(root, file)
  93. c_files.append(f'// {path}\n')
  94. content = read_file(path)
  95. content = re.sub(
  96. r'#include\s+"pocketpy/(.+)"\s*',
  97. _replace,
  98. content,
  99. )
  100. c_files.append(content)
  101. c_files.append('\n')
  102. return ''.join(c_files)
  103. def merge_h_files():
  104. h_files = [COPYRIGHT, '#pragma once']
  105. def _replace(m):
  106. path = m.group(1)
  107. if path.startswith('xmacros/'):
  108. return read_file(f'{ROOT}/{path}') + '\n'
  109. return '' # remove include
  110. for path in PUBLIC_HEADERS:
  111. content = read_file(f'{ROOT}/{path}')
  112. content = content.replace('#pragma once', '')
  113. content = re.sub(
  114. r'#include\s+"pocketpy/(.+)"\s*',
  115. _replace,
  116. content,
  117. )
  118. h_files.append(content)
  119. return '\n'.join(h_files)
  120. write_file('amalgamated/pocketpy.c', merge_c_files())
  121. write_file('amalgamated/pocketpy.h', merge_h_files())
  122. shutil.copy("src2/main.c", "amalgamated/main.c")
  123. if sys.platform in ['linux', 'darwin']:
  124. ok = os.system("clang -o main amalgamated/pocketpy.c amalgamated/main.c -O1 --std=c11 -lm")
  125. if ok == 0:
  126. print("Test build success!")
  127. print("amalgamated/pocketpy.h")
  128. # def sync(path):
  129. # shutil.copy("amalgamated/pocketpy.h", os.path.join(path, "pocketpy.h"))
  130. # with open(os.path.join(path, "pocketpy.cpp"), "wt", encoding='utf-8', newline='\n') as f:
  131. # f.write("#include \"pocketpy.h\"\n")
  132. # sync("plugins/macos/pocketpy")