gendynapi.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. #!/usr/bin/env python3
  2. # Simple DirectMedia Layer
  3. # Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
  4. #
  5. # This software is provided 'as-is', without any express or implied
  6. # warranty. In no event will the authors be held liable for any damages
  7. # arising from the use of this software.
  8. #
  9. # Permission is granted to anyone to use this software for any purpose,
  10. # including commercial applications, and to alter it and redistribute it
  11. # freely, subject to the following restrictions:
  12. #
  13. # 1. The origin of this software must not be misrepresented; you must not
  14. # claim that you wrote the original software. If you use this software
  15. # in a product, an acknowledgment in the product documentation would be
  16. # appreciated but is not required.
  17. # 2. Altered source versions must be plainly marked as such, and must not be
  18. # misrepresented as being the original software.
  19. # 3. This notice may not be removed or altered from any source distribution.
  20. # WHAT IS THIS?
  21. # When you add a public API to SDL, please run this script, make sure the
  22. # output looks sane (git diff, it adds to existing files), and commit it.
  23. # It keeps the dynamic API jump table operating correctly.
  24. #
  25. # OS-specific API:
  26. # After running the script, you have to manually add #ifdef __WIN32__
  27. # or similar around the function in 'SDL_dynapi_procs.h'
  28. #
  29. import argparse
  30. import json
  31. import os
  32. import pathlib
  33. import pprint
  34. import re
  35. SDL_ROOT = pathlib.Path(__file__).resolve().parents[2]
  36. SDL_INCLUDE_DIR = SDL_ROOT / "include/SDL3"
  37. SDL_DYNAPI_PROCS_H = SDL_ROOT / "src/dynapi/SDL_dynapi_procs.h"
  38. SDL_DYNAPI_OVERRIDES_H = SDL_ROOT / "src/dynapi/SDL_dynapi_overrides.h"
  39. SDL_DYNAPI_SYM = SDL_ROOT / "src/dynapi/SDL_dynapi.sym"
  40. SDL_ANDROID_C = SDL_ROOT / "src/core/android/SDL_android.c"
  41. SDL_ANDROID_HID_CPP = SDL_ROOT / "src/hidapi/android/hid.cpp"
  42. full_API = []
  43. def main():
  44. # Parse 'sdl_dynapi_procs_h' file to find existing functions
  45. existing_procs = find_existing_procs()
  46. # Get list of SDL headers
  47. sdl_list_includes = get_header_list()
  48. reg_externC = re.compile('.*extern[ "]*C[ "].*')
  49. reg_comment_remove_content = re.compile('\/\*.*\*/')
  50. reg_parsing_function = re.compile('(.*SDLCALL[^\(\)]*) ([a-zA-Z0-9_]+) *\((.*)\) *;.*')
  51. #eg:
  52. # void (SDLCALL *callback)(void*, int)
  53. # \1(\2)\3
  54. reg_parsing_callback = re.compile('([^\(\)]*)\(([^\(\)]+)\)(.*)')
  55. for filename in sdl_list_includes:
  56. if args.debug:
  57. print("Parse header: %s" % filename)
  58. input = open(filename)
  59. parsing_function = False
  60. current_func = ""
  61. parsing_comment = False
  62. current_comment = ""
  63. for line in input:
  64. # Discard pre-processor directives ^#.*
  65. if line.startswith("#"):
  66. continue
  67. # Discard "extern C" line
  68. match = reg_externC.match(line)
  69. if match:
  70. continue
  71. # Remove one line comment /* ... */
  72. # eg: extern DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */);
  73. line = reg_comment_remove_content.sub('', line)
  74. # Get the comment block /* ... */ across several lines
  75. match_start = "/*" in line
  76. match_end = "*/" in line
  77. if match_start and match_end:
  78. continue
  79. if match_start:
  80. parsing_comment = True
  81. current_comment = line
  82. continue
  83. if match_end:
  84. parsing_comment = False
  85. current_comment += line
  86. continue
  87. if parsing_comment:
  88. current_comment += line
  89. continue
  90. # Get the function prototype across several lines
  91. if parsing_function:
  92. # Append to the current function
  93. current_func += " "
  94. current_func += line.strip()
  95. else:
  96. # if is contains "extern", start grabbing
  97. if "extern" not in line:
  98. continue
  99. # Start grabbing the new function
  100. current_func = line.strip()
  101. parsing_function = True;
  102. # If it contains ';', then the function is complete
  103. if ";" not in current_func:
  104. continue
  105. # Got function/comment, reset vars
  106. parsing_function = False;
  107. func = current_func
  108. comment = current_comment
  109. current_func = ""
  110. current_comment = ""
  111. # Discard if it doesn't contain 'SDLCALL'
  112. if "SDLCALL" not in func:
  113. if args.debug:
  114. print(" Discard: " + func)
  115. continue
  116. if args.debug:
  117. print(" Raw data: " + func);
  118. # Replace unusual stuff...
  119. func = func.replace("SDL_PRINTF_VARARG_FUNC(1)", "");
  120. func = func.replace("SDL_PRINTF_VARARG_FUNC(2)", "");
  121. func = func.replace("SDL_PRINTF_VARARG_FUNC(3)", "");
  122. func = func.replace("SDL_SCANF_VARARG_FUNC(2)", "");
  123. func = func.replace("__attribute__((analyzer_noreturn))", "");
  124. func = func.replace("SDL_MALLOC", "");
  125. func = func.replace("SDL_ALLOC_SIZE2(1, 2)", "");
  126. func = func.replace("SDL_ALLOC_SIZE(2)", "");
  127. func = re.sub(" SDL_ACQUIRE\(.*\)", "", func);
  128. func = re.sub(" SDL_TRY_ACQUIRE\(.*\)", "", func);
  129. func = re.sub(" SDL_RELEASE\(.*\)", "", func);
  130. # Should be a valid function here
  131. match = reg_parsing_function.match(func)
  132. if not match:
  133. print("Cannot parse: "+ func)
  134. exit(-1)
  135. func_ret = match.group(1)
  136. func_name = match.group(2)
  137. func_params = match.group(3)
  138. #
  139. # Parse return value
  140. #
  141. func_ret = func_ret.replace('extern', ' ')
  142. func_ret = func_ret.replace('SDLCALL', ' ')
  143. func_ret = func_ret.replace('DECLSPEC', ' ')
  144. # Remove trailling spaces in front of '*'
  145. tmp = ""
  146. while func_ret != tmp:
  147. tmp = func_ret
  148. func_ret = func_ret.replace(' ', ' ')
  149. func_ret = func_ret.replace(' *', '*')
  150. func_ret = func_ret.strip()
  151. #
  152. # Parse parameters
  153. #
  154. func_params = func_params.strip()
  155. if func_params == "":
  156. func_params = "void"
  157. # Identify each function parameters with type and name
  158. # (eventually there are callbacks of several parameters)
  159. tmp = func_params.split(',')
  160. tmp2 = []
  161. param = ""
  162. for t in tmp:
  163. if param == "":
  164. param = t
  165. else:
  166. param = param + "," + t
  167. # Identify a callback or parameter when there is same count of '(' and ')'
  168. if param.count('(') == param.count(')'):
  169. tmp2.append(param.strip())
  170. param = ""
  171. # Process each parameters, separation name and type
  172. func_param_type = []
  173. func_param_name = []
  174. for t in tmp2:
  175. if t == "void":
  176. func_param_type.append(t)
  177. func_param_name.append("")
  178. continue
  179. if t == "...":
  180. func_param_type.append(t)
  181. func_param_name.append("")
  182. continue
  183. param_name = ""
  184. # parameter is a callback
  185. if '(' in t:
  186. match = reg_parsing_callback.match(t)
  187. if not match:
  188. print("cannot parse callback: " + t);
  189. exit(-1)
  190. a = match.group(1).strip()
  191. b = match.group(2).strip()
  192. c = match.group(3).strip()
  193. try:
  194. (param_type, param_name) = b.rsplit('*', 1)
  195. except:
  196. param_type = t;
  197. param_name = "param_name_not_specified"
  198. # bug rsplit ??
  199. if param_name == "":
  200. param_name = "param_name_not_specified"
  201. # recontruct a callback name for future parsing
  202. func_param_type.append(a + " (" + param_type.strip() + " *REWRITE_NAME)" + c)
  203. func_param_name.append(param_name.strip())
  204. continue
  205. # array like "char *buf[]"
  206. has_array = False
  207. if t.endswith("[]"):
  208. t = t.replace("[]", "")
  209. has_array = True
  210. # pointer
  211. if '*' in t:
  212. try:
  213. (param_type, param_name) = t.rsplit('*', 1)
  214. except:
  215. param_type = t;
  216. param_name = "param_name_not_specified"
  217. # bug rsplit ??
  218. if param_name == "":
  219. param_name = "param_name_not_specified"
  220. val = param_type.strip() + "*REWRITE_NAME"
  221. # Remove trailling spaces in front of '*'
  222. tmp = ""
  223. while val != tmp:
  224. tmp = val
  225. val = val.replace(' ', ' ')
  226. val = val.replace(' *', '*')
  227. # first occurence
  228. val = val.replace('*', ' *', 1)
  229. val = val.strip()
  230. else: # non pointer
  231. # cut-off last word on
  232. try:
  233. (param_type, param_name) = t.rsplit(' ', 1)
  234. except:
  235. param_type = t;
  236. param_name = "param_name_not_specified"
  237. val = param_type.strip() + " REWRITE_NAME"
  238. # set back array
  239. if has_array:
  240. val += "[]"
  241. func_param_type.append(val)
  242. func_param_name.append(param_name.strip())
  243. new_proc = {}
  244. # Return value type
  245. new_proc['retval'] = func_ret
  246. # List of parameters (type + anonymized param name 'REWRITE_NAME')
  247. new_proc['parameter'] = func_param_type
  248. # Real parameter name, or 'param_name_not_specified'
  249. new_proc['parameter_name'] = func_param_name
  250. # Function name
  251. new_proc['name'] = func_name
  252. # Header file
  253. new_proc['header'] = os.path.basename(filename)
  254. # Function comment
  255. new_proc['comment'] = comment
  256. full_API.append(new_proc)
  257. if args.debug:
  258. pprint.pprint(new_proc);
  259. print("\n")
  260. if func_name not in existing_procs:
  261. print("NEW " + func)
  262. add_dyn_api(new_proc)
  263. # For-End line in input
  264. input.close()
  265. # For-End parsing all files of sdl_list_includes
  266. # Dump API into a json file
  267. full_API_json()
  268. # Add all native Android functions to SDL_dynapi.sym
  269. write_android_symbols_to_sym()
  270. # Dump API into a json file
  271. def full_API_json():
  272. if args.dump:
  273. filename = 'sdl.json'
  274. with open(filename, 'w') as f:
  275. json.dump(full_API, f, indent=4, sort_keys=True)
  276. print("dump API to '%s'" % filename);
  277. # Parse 'sdl_dynapi_procs_h' file to find existing functions
  278. def find_existing_procs():
  279. reg = re.compile('SDL_DYNAPI_PROC\([^,]*,([^,]*),.*\)')
  280. ret = []
  281. input = open(SDL_DYNAPI_PROCS_H)
  282. for line in input:
  283. match = reg.match(line)
  284. if not match:
  285. continue
  286. existing_func = match.group(1)
  287. ret.append(existing_func);
  288. # print(existing_func)
  289. input.close()
  290. return ret
  291. # Get list of SDL headers
  292. def get_header_list():
  293. reg = re.compile('^.*\.h$')
  294. ret = []
  295. tmp = os.listdir(SDL_INCLUDE_DIR)
  296. for f in tmp:
  297. # Only *.h files
  298. match = reg.match(f)
  299. if not match:
  300. if args.debug:
  301. print("Skip %s" % f)
  302. continue
  303. ret.append(SDL_INCLUDE_DIR / f)
  304. return ret
  305. # Write the new API in files: _procs.h _overrivides.h and .sym
  306. def add_dyn_api(proc):
  307. func_name = proc['name']
  308. func_ret = proc['retval']
  309. func_argtype = proc['parameter']
  310. # File: SDL_dynapi_procs.h
  311. #
  312. # Add at last
  313. # SDL_DYNAPI_PROC(SDL_EGLConfig,SDL_EGL_GetCurrentEGLConfig,(void),(),return)
  314. f = open(SDL_DYNAPI_PROCS_H, "a")
  315. dyn_proc = "SDL_DYNAPI_PROC(" + func_ret + "," + func_name + ",("
  316. i = ord('a')
  317. remove_last = False
  318. for argtype in func_argtype:
  319. # Special case, void has no parameter name
  320. if argtype == "void":
  321. dyn_proc += "void"
  322. continue
  323. # Var name: a, b, c, ...
  324. varname = chr(i)
  325. i += 1
  326. tmp = argtype.replace("REWRITE_NAME", varname)
  327. dyn_proc += tmp + ", "
  328. remove_last = True
  329. # remove last 2 char ', '
  330. if remove_last:
  331. dyn_proc = dyn_proc[:-1]
  332. dyn_proc = dyn_proc[:-1]
  333. dyn_proc += "),("
  334. i = ord('a')
  335. remove_last = False
  336. for argtype in func_argtype:
  337. # Special case, void has no parameter name
  338. if argtype == "void":
  339. continue
  340. # Special case, '...' has no parameter name
  341. if argtype == "...":
  342. continue
  343. # Var name: a, b, c, ...
  344. varname = chr(i)
  345. i += 1
  346. dyn_proc += varname + ","
  347. remove_last = True
  348. # remove last char ','
  349. if remove_last:
  350. dyn_proc = dyn_proc[:-1]
  351. dyn_proc += "),"
  352. if func_ret != "void":
  353. dyn_proc += "return"
  354. dyn_proc += ")"
  355. f.write(dyn_proc + "\n")
  356. f.close()
  357. # File: SDL_dynapi_overrides.h
  358. #
  359. # Add at last
  360. # "#define SDL_DelayNS SDL_DelayNS_REAL
  361. f = open(SDL_DYNAPI_OVERRIDES_H, "a")
  362. f.write("#define " + func_name + " " + func_name + "_REAL\n")
  363. f.close()
  364. # File: SDL_dynapi.sym
  365. #
  366. # Add before "extra symbols go here" line
  367. input = open(SDL_DYNAPI_SYM)
  368. new_input = []
  369. for line in input:
  370. if "extra symbols go here" in line:
  371. new_input.append(" " + func_name + ";\n")
  372. new_input.append(line)
  373. input.close()
  374. f = open(SDL_DYNAPI_SYM, 'w')
  375. for line in new_input:
  376. f.write(line)
  377. f.close()
  378. def extract_symbols_sdl_android_c():
  379. sdl_android_c_source = SDL_ANDROID_C.read_text()
  380. prefix_match = re.search(r"#define SDL_JAVA_PREFIX\s+(?P<prefix>\S+)", sdl_android_c_source)
  381. prefix = prefix_match["prefix"]
  382. wrapper_names = []
  383. wrappers = set()
  384. for m in re.finditer(r"JNIEXPORT[\s]+(?P<ret>[a-z_]+)[\s]+JNICALL[\s]+(?P<wrapper>SDL_JAVA[A-Z_]+)\((?P<name>[a-zA-Z0-9_]+)\)", sdl_android_c_source, flags=re.M):
  385. wrappers.add(m["wrapper"])
  386. wrapper_names.append({"wrapper": m["wrapper"], "name": m["name"]})
  387. wrapper2prefix = {}
  388. for wrapper in wrappers:
  389. s = re.search(r"#define\s+"+wrapper+r"\([a-z]+\)\s+CONCAT1\(SDL_JAVA_PREFIX,\s*(?P<prefix>[a-zA-Z0-9_]+),\s*function\)", sdl_android_c_source)
  390. wrapper2prefix[wrapper] = s["prefix"]
  391. symbols = set()
  392. for wrapper_name in wrapper_names:
  393. symbols.add("Java_" + prefix + "_" + wrapper2prefix[wrapper_name["wrapper"]] + "_" + wrapper_name["name"])
  394. symbols.add("JNI_OnLoad")
  395. symbols = list(symbols)
  396. return symbols
  397. def extract_symbols_andoid_sdl_hid_cpp():
  398. hid_cpp_source = SDL_ANDROID_HID_CPP.read_text()
  399. prefix_match = re.search(r"#define SDL_JAVA_PREFIX\s+(?P<prefix>\S+)", hid_cpp_source)
  400. prefix = prefix_match["prefix"]
  401. wrapper_names = []
  402. wrappers = set()
  403. for m in re.finditer(r"JNIEXPORT[\s]+(?P<ret>[a-z_]+)[\s]+JNICALL[\s]+(?P<wrapper>HID_[A-Z_]+)\((?P<name>[a-zA-Z0-9_]+)\)", hid_cpp_source, flags=re.M):
  404. wrappers.add(m["wrapper"])
  405. wrapper_names.append({"wrapper": m["wrapper"], "name": m["name"]})
  406. wrapper2prefix = {}
  407. for wrapper in wrappers:
  408. s = re.search(r"#define\s+"+wrapper+r"\([a-z]+\)\s+CONCAT1\(SDL_JAVA_PREFIX,\s*(?P<prefix>[a-zA-Z0-9_]+),\s*function\)", hid_cpp_source)
  409. wrapper2prefix[wrapper] = s["prefix"]
  410. symbols = set()
  411. for wrapper_name in wrapper_names:
  412. symbols.add("Java_" + prefix + "_" + wrapper2prefix[wrapper_name["wrapper"]] + "_" + wrapper_name["name"])
  413. symbols = list(symbols)
  414. symbols.sort()
  415. return symbols
  416. def extract_android_symbols():
  417. symbols = extract_symbols_sdl_android_c() + extract_symbols_andoid_sdl_hid_cpp()
  418. symbols.sort()
  419. return symbols
  420. def write_android_symbols_to_sym():
  421. android_symbols = extract_android_symbols()
  422. new_input = []
  423. ignore_current = False
  424. for line in SDL_DYNAPI_SYM.open():
  425. if "Android symbols end here" in line:
  426. ignore_current = False
  427. for android_symbol in android_symbols:
  428. new_input.append(" " + android_symbol + ";\n")
  429. if ignore_current:
  430. continue
  431. if "Android symbols start here" in line:
  432. ignore_current = True
  433. new_input.append(line)
  434. with SDL_DYNAPI_SYM.open("w") as f:
  435. for line in new_input:
  436. f.write(line)
  437. if __name__ == '__main__':
  438. parser = argparse.ArgumentParser()
  439. parser.add_argument('--dump', help='output all SDL API into a .json file', action='store_true')
  440. parser.add_argument('--debug', help='add debug traces', action='store_true')
  441. args = parser.parse_args()
  442. try:
  443. main()
  444. except Exception as e:
  445. print(e)
  446. exit(-1)
  447. print("done!")
  448. exit(0)