1
0

amalgamate.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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', 'vmath.h', 'pocketpy.h']
  10. COPYRIGHT = '''/*
  11. * Copyright (c) 2025 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 = [
  67. COPYRIGHT,
  68. '\n',
  69. '#define PK_IS_AMALGAMATED_C',
  70. '\n',
  71. '#include "pocketpy.h"',
  72. '\n'
  73. ]
  74. # merge internal headers
  75. internal_h = []
  76. while True:
  77. for h in headers.values():
  78. if not h.dependencies:
  79. break
  80. else:
  81. if headers:
  82. print(headers)
  83. raise RuntimeError("Circular dependencies detected")
  84. break
  85. # print(h.path)
  86. internal_h.append(h.text())
  87. del headers[h.path]
  88. for h2 in headers.values():
  89. h2.dependencies = [d for d in h2.dependencies if d != h.path]
  90. c_files.extend(internal_h)
  91. def _replace(m):
  92. path = m.group(1)
  93. if path.startswith('xmacros/'):
  94. return read_file(f'{ROOT}/{path}') + '\n'
  95. return '' # remove include
  96. for root, _, files in os.walk('src/'):
  97. for file in files:
  98. if file.endswith('.c'):
  99. path = os.path.join(root, file)
  100. c_files.append(f'// {path}\n')
  101. content = read_file(path)
  102. content = re.sub(
  103. r'#include\s+"pocketpy/(.+)"\s*',
  104. _replace,
  105. content,
  106. )
  107. c_files.append(content)
  108. c_files.append('\n')
  109. return ''.join(c_files)
  110. def merge_h_files():
  111. h_files = [
  112. COPYRIGHT,
  113. '#pragma once',
  114. '\n',
  115. '#define PK_IS_PUBLIC_INCLUDE',
  116. ]
  117. def _replace(m):
  118. path = m.group(1)
  119. if path.startswith('xmacros/'):
  120. return read_file(f'{ROOT}/{path}') + '\n'
  121. return '' # remove include
  122. for path in PUBLIC_HEADERS:
  123. content = read_file(f'{ROOT}/{path}')
  124. content = content.replace('#pragma once', '')
  125. content = re.sub(
  126. r'#include\s+"pocketpy/(.+)"\s*',
  127. _replace,
  128. content,
  129. )
  130. h_files.append(content)
  131. return '\n'.join(h_files)
  132. write_file('amalgamated/pocketpy.c', merge_c_files())
  133. write_file('amalgamated/pocketpy.h', merge_h_files())
  134. shutil.copy("src2/main.c", "amalgamated/main.c")
  135. def checked_sh(cmd):
  136. ok = os.system(cmd)
  137. assert ok == 0, f"command failed: {cmd}"
  138. if sys.platform in ['linux', 'darwin']:
  139. common_flags = "-O1 --std=c11 -lm -ldl -lpthread -Iamalgamated"
  140. checked_sh(f"gcc -o main amalgamated/pocketpy.c src2/example.c {common_flags}")
  141. checked_sh("./main && rm -f ./main")
  142. checked_sh(f"gcc -o main amalgamated/pocketpy.c amalgamated/main.c {common_flags}")
  143. print("amalgamated/pocketpy.h")