1
0

amalgamate.py 4.0 KB

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