freestanding.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. #!/usr/bin/env python3
  2. # ################################################################
  3. # Copyright (c) 2021-2021, Facebook, Inc.
  4. # All rights reserved.
  5. #
  6. # This source code is licensed under both the BSD-style license (found in the
  7. # LICENSE file in the root directory of this source tree) and the GPLv2 (found
  8. # in the COPYING file in the root directory of this source tree).
  9. # You may select, at your option, one of the above-listed licenses.
  10. # ##########################################################################
  11. import argparse
  12. import contextlib
  13. import os
  14. import re
  15. import shutil
  16. import sys
  17. from typing import Optional
  18. INCLUDED_SUBDIRS = ["common", "compress", "decompress"]
  19. SKIPPED_FILES = [
  20. "common/mem.h",
  21. "common/zstd_deps.h",
  22. "common/pool.c",
  23. "common/pool.h",
  24. "common/threading.c",
  25. "common/threading.h",
  26. "common/zstd_trace.h",
  27. "compress/zstdmt_compress.h",
  28. "compress/zstdmt_compress.c",
  29. ]
  30. XXHASH_FILES = [
  31. "common/xxhash.c",
  32. "common/xxhash.h",
  33. ]
  34. class FileLines(object):
  35. def __init__(self, filename):
  36. self.filename = filename
  37. with open(self.filename, "r") as f:
  38. self.lines = f.readlines()
  39. def write(self):
  40. with open(self.filename, "w") as f:
  41. f.write("".join(self.lines))
  42. class PartialPreprocessor(object):
  43. """
  44. Looks for simple ifdefs and ifndefs and replaces them.
  45. Handles && and ||.
  46. Has fancy logic to handle translating elifs to ifs.
  47. Only looks for macros in the first part of the expression with no
  48. parens.
  49. Does not handle multi-line macros (only looks in first line).
  50. """
  51. def __init__(self, defs: [(str, Optional[str])], replaces: [(str, str)], undefs: [str]):
  52. MACRO_GROUP = r"(?P<macro>[a-zA-Z_][a-zA-Z_0-9]*)"
  53. ELIF_GROUP = r"(?P<elif>el)?"
  54. OP_GROUP = r"(?P<op>&&|\|\|)?"
  55. self._defs = {macro:value for macro, value in defs}
  56. self._replaces = {macro:value for macro, value in replaces}
  57. self._defs.update(self._replaces)
  58. self._undefs = set(undefs)
  59. self._define = re.compile(r"\s*#\s*define")
  60. self._if = re.compile(r"\s*#\s*if")
  61. self._elif = re.compile(r"\s*#\s*(?P<elif>el)if")
  62. self._else = re.compile(r"\s*#\s*(?P<else>else)")
  63. self._endif = re.compile(r"\s*#\s*endif")
  64. self._ifdef = re.compile(fr"\s*#\s*if(?P<not>n)?def {MACRO_GROUP}\s*")
  65. self._if_defined = re.compile(
  66. fr"\s*#\s*{ELIF_GROUP}if\s+(?P<not>!)?\s*defined\s*\(\s*{MACRO_GROUP}\s*\)\s*{OP_GROUP}"
  67. )
  68. self._if_defined_value = re.compile(
  69. fr"\s*#\s*{ELIF_GROUP}if\s+defined\s*\(\s*{MACRO_GROUP}\s*\)\s*"
  70. fr"(?P<op>&&)\s*"
  71. fr"(?P<openp>\()?\s*"
  72. fr"(?P<macro2>[a-zA-Z_][a-zA-Z_0-9]*)\s*"
  73. fr"(?P<cmp>[=><!]+)\s*"
  74. fr"(?P<value>[0-9]*)\s*"
  75. fr"(?P<closep>\))?\s*"
  76. )
  77. self._if_true = re.compile(
  78. fr"\s*#\s*{ELIF_GROUP}if\s+{MACRO_GROUP}\s*{OP_GROUP}"
  79. )
  80. self._c_comment = re.compile(r"/\*.*?\*/")
  81. self._cpp_comment = re.compile(r"//")
  82. def _log(self, *args, **kwargs):
  83. print(*args, **kwargs)
  84. def _strip_comments(self, line):
  85. # First strip c-style comments (may include //)
  86. while True:
  87. m = self._c_comment.search(line)
  88. if m is None:
  89. break
  90. line = line[:m.start()] + line[m.end():]
  91. # Then strip cpp-style comments
  92. m = self._cpp_comment.search(line)
  93. if m is not None:
  94. line = line[:m.start()]
  95. return line
  96. def _fixup_indentation(self, macro, replace: [str]):
  97. if len(replace) == 0:
  98. return replace
  99. if len(replace) == 1 and self._define.match(replace[0]) is None:
  100. # If there is only one line, only replace defines
  101. return replace
  102. all_pound = True
  103. for line in replace:
  104. if not line.startswith('#'):
  105. all_pound = False
  106. if all_pound:
  107. replace = [line[1:] for line in replace]
  108. min_spaces = len(replace[0])
  109. for line in replace:
  110. spaces = 0
  111. for i, c in enumerate(line):
  112. if c != ' ':
  113. # Non-preprocessor line ==> skip the fixup
  114. if not all_pound and c != '#':
  115. return replace
  116. spaces = i
  117. break
  118. min_spaces = min(min_spaces, spaces)
  119. replace = [line[min_spaces:] for line in replace]
  120. if all_pound:
  121. replace = ["#" + line for line in replace]
  122. return replace
  123. def _handle_if_block(self, macro, idx, is_true, prepend):
  124. """
  125. Remove the #if or #elif block starting on this line.
  126. """
  127. REMOVE_ONE = 0
  128. KEEP_ONE = 1
  129. REMOVE_REST = 2
  130. if is_true:
  131. state = KEEP_ONE
  132. else:
  133. state = REMOVE_ONE
  134. line = self._inlines[idx]
  135. is_if = self._if.match(line) is not None
  136. assert is_if or self._elif.match(line) is not None
  137. depth = 0
  138. start_idx = idx
  139. idx += 1
  140. replace = prepend
  141. finished = False
  142. while idx < len(self._inlines):
  143. line = self._inlines[idx]
  144. # Nested if statement
  145. if self._if.match(line):
  146. depth += 1
  147. idx += 1
  148. continue
  149. # We're inside a nested statement
  150. if depth > 0:
  151. if self._endif.match(line):
  152. depth -= 1
  153. idx += 1
  154. continue
  155. # We're at the original depth
  156. # Looking only for an endif.
  157. # We've found a true statement, but haven't
  158. # completely elided the if block, so we just
  159. # remove the remainder.
  160. if state == REMOVE_REST:
  161. if self._endif.match(line):
  162. if is_if:
  163. # Remove the endif because we took the first if
  164. idx += 1
  165. finished = True
  166. break
  167. idx += 1
  168. continue
  169. if state == KEEP_ONE:
  170. m = self._elif.match(line)
  171. if self._endif.match(line):
  172. replace += self._inlines[start_idx + 1:idx]
  173. idx += 1
  174. finished = True
  175. break
  176. if self._elif.match(line) or self._else.match(line):
  177. replace += self._inlines[start_idx + 1:idx]
  178. state = REMOVE_REST
  179. idx += 1
  180. continue
  181. if state == REMOVE_ONE:
  182. m = self._elif.match(line)
  183. if m is not None:
  184. if is_if:
  185. idx += 1
  186. b = m.start('elif')
  187. e = m.end('elif')
  188. assert e - b == 2
  189. replace.append(line[:b] + line[e:])
  190. finished = True
  191. break
  192. m = self._else.match(line)
  193. if m is not None:
  194. if is_if:
  195. idx += 1
  196. while self._endif.match(self._inlines[idx]) is None:
  197. replace.append(self._inlines[idx])
  198. idx += 1
  199. idx += 1
  200. finished = True
  201. break
  202. if self._endif.match(line):
  203. if is_if:
  204. # Remove the endif because no other elifs
  205. idx += 1
  206. finished = True
  207. break
  208. idx += 1
  209. continue
  210. if not finished:
  211. raise RuntimeError("Unterminated if block!")
  212. replace = self._fixup_indentation(macro, replace)
  213. self._log(f"\tHardwiring {macro}")
  214. if start_idx > 0:
  215. self._log(f"\t\t {self._inlines[start_idx - 1][:-1]}")
  216. for x in range(start_idx, idx):
  217. self._log(f"\t\t- {self._inlines[x][:-1]}")
  218. for line in replace:
  219. self._log(f"\t\t+ {line[:-1]}")
  220. if idx < len(self._inlines):
  221. self._log(f"\t\t {self._inlines[idx][:-1]}")
  222. return idx, replace
  223. def _preprocess_once(self):
  224. outlines = []
  225. idx = 0
  226. changed = False
  227. while idx < len(self._inlines):
  228. line = self._inlines[idx]
  229. sline = self._strip_comments(line)
  230. m = self._ifdef.fullmatch(sline)
  231. if_true = False
  232. if m is None:
  233. m = self._if_defined_value.fullmatch(sline)
  234. if m is None:
  235. m = self._if_defined.match(sline)
  236. if m is None:
  237. m = self._if_true.match(sline)
  238. if_true = (m is not None)
  239. if m is None:
  240. outlines.append(line)
  241. idx += 1
  242. continue
  243. groups = m.groupdict()
  244. macro = groups['macro']
  245. op = groups.get('op')
  246. if not (macro in self._defs or macro in self._undefs):
  247. outlines.append(line)
  248. idx += 1
  249. continue
  250. defined = macro in self._defs
  251. # Needed variables set:
  252. # resolved: Is the statement fully resolved?
  253. # is_true: If resolved, is the statement true?
  254. ifdef = False
  255. if if_true:
  256. if not defined:
  257. outlines.append(line)
  258. idx += 1
  259. continue
  260. defined_value = self._defs[macro]
  261. is_int = True
  262. try:
  263. defined_value = int(defined_value)
  264. except TypeError:
  265. is_int = False
  266. except ValueError:
  267. is_int = False
  268. resolved = is_int
  269. is_true = (defined_value != 0)
  270. if resolved and op is not None:
  271. if op == '&&':
  272. resolved = not is_true
  273. else:
  274. assert op == '||'
  275. resolved = is_true
  276. else:
  277. ifdef = groups.get('not') is None
  278. elseif = groups.get('elif') is not None
  279. macro2 = groups.get('macro2')
  280. cmp = groups.get('cmp')
  281. value = groups.get('value')
  282. openp = groups.get('openp')
  283. closep = groups.get('closep')
  284. is_true = (ifdef == defined)
  285. resolved = True
  286. if op is not None:
  287. if op == '&&':
  288. resolved = not is_true
  289. else:
  290. assert op == '||'
  291. resolved = is_true
  292. if macro2 is not None and not resolved:
  293. assert ifdef and defined and op == '&&' and cmp is not None
  294. # If the statment is true, but we have a single value check, then
  295. # check the value.
  296. defined_value = self._defs[macro]
  297. are_ints = True
  298. try:
  299. defined_value = int(defined_value)
  300. value = int(value)
  301. except TypeError:
  302. are_ints = False
  303. except ValueError:
  304. are_ints = False
  305. if (
  306. macro == macro2 and
  307. ((openp is None) == (closep is None)) and
  308. are_ints
  309. ):
  310. resolved = True
  311. if cmp == '<':
  312. is_true = defined_value < value
  313. elif cmp == '<=':
  314. is_true = defined_value <= value
  315. elif cmp == '==':
  316. is_true = defined_value == value
  317. elif cmp == '!=':
  318. is_true = defined_value != value
  319. elif cmp == '>=':
  320. is_true = defined_value >= value
  321. elif cmp == '>':
  322. is_true = defined_value > value
  323. else:
  324. resolved = False
  325. if op is not None and not resolved:
  326. # Remove the first op in the line + spaces
  327. if op == '&&':
  328. opre = op
  329. else:
  330. assert op == '||'
  331. opre = r'\|\|'
  332. needle = re.compile(fr"(?P<if>\s*#\s*(el)?if\s+).*?(?P<op>{opre}\s*)")
  333. match = needle.match(line)
  334. assert match is not None
  335. newline = line[:match.end('if')] + line[match.end('op'):]
  336. self._log(f"\tHardwiring partially resolved {macro}")
  337. self._log(f"\t\t- {line[:-1]}")
  338. self._log(f"\t\t+ {newline[:-1]}")
  339. outlines.append(newline)
  340. idx += 1
  341. continue
  342. # Skip any statements we cannot fully compute
  343. if not resolved:
  344. outlines.append(line)
  345. idx += 1
  346. continue
  347. prepend = []
  348. if macro in self._replaces:
  349. assert not ifdef
  350. assert op is None
  351. value = self._replaces.pop(macro)
  352. prepend = [f"#define {macro} {value}\n"]
  353. idx, replace = self._handle_if_block(macro, idx, is_true, prepend)
  354. outlines += replace
  355. changed = True
  356. return changed, outlines
  357. def preprocess(self, filename):
  358. with open(filename, 'r') as f:
  359. self._inlines = f.readlines()
  360. changed = True
  361. iters = 0
  362. while changed:
  363. iters += 1
  364. changed, outlines = self._preprocess_once()
  365. self._inlines = outlines
  366. with open(filename, 'w') as f:
  367. f.write(''.join(self._inlines))
  368. class Freestanding(object):
  369. def __init__(
  370. self, zstd_deps: str, mem: str, source_lib: str, output_lib: str,
  371. external_xxhash: bool, xxh64_state: Optional[str],
  372. xxh64_prefix: Optional[str], rewritten_includes: [(str, str)],
  373. defs: [(str, Optional[str])], replaces: [(str, str)],
  374. undefs: [str], excludes: [str], seds: [str],
  375. ):
  376. self._zstd_deps = zstd_deps
  377. self._mem = mem
  378. self._src_lib = source_lib
  379. self._dst_lib = output_lib
  380. self._external_xxhash = external_xxhash
  381. self._xxh64_state = xxh64_state
  382. self._xxh64_prefix = xxh64_prefix
  383. self._rewritten_includes = rewritten_includes
  384. self._defs = defs
  385. self._replaces = replaces
  386. self._undefs = undefs
  387. self._excludes = excludes
  388. self._seds = seds
  389. def _dst_lib_file_paths(self):
  390. """
  391. Yields all the file paths in the dst_lib.
  392. """
  393. for root, dirname, filenames in os.walk(self._dst_lib):
  394. for filename in filenames:
  395. filepath = os.path.join(root, filename)
  396. yield filepath
  397. def _log(self, *args, **kwargs):
  398. print(*args, **kwargs)
  399. def _copy_file(self, lib_path):
  400. suffixes = [".c", ".h", ".S"]
  401. if not any((lib_path.endswith(suffix) for suffix in suffixes)):
  402. return
  403. if lib_path in SKIPPED_FILES:
  404. self._log(f"\tSkipping file: {lib_path}")
  405. return
  406. if self._external_xxhash and lib_path in XXHASH_FILES:
  407. self._log(f"\tSkipping xxhash file: {lib_path}")
  408. return
  409. src_path = os.path.join(self._src_lib, lib_path)
  410. dst_path = os.path.join(self._dst_lib, lib_path)
  411. self._log(f"\tCopying: {src_path} -> {dst_path}")
  412. shutil.copyfile(src_path, dst_path)
  413. def _copy_source_lib(self):
  414. self._log("Copying source library into output library")
  415. assert os.path.exists(self._src_lib)
  416. os.makedirs(self._dst_lib, exist_ok=True)
  417. self._copy_file("zstd.h")
  418. self._copy_file("zstd_errors.h")
  419. for subdir in INCLUDED_SUBDIRS:
  420. src_dir = os.path.join(self._src_lib, subdir)
  421. dst_dir = os.path.join(self._dst_lib, subdir)
  422. assert os.path.exists(src_dir)
  423. os.makedirs(dst_dir, exist_ok=True)
  424. for filename in os.listdir(src_dir):
  425. lib_path = os.path.join(subdir, filename)
  426. self._copy_file(lib_path)
  427. def _copy_zstd_deps(self):
  428. dst_zstd_deps = os.path.join(self._dst_lib, "common", "zstd_deps.h")
  429. self._log(f"Copying zstd_deps: {self._zstd_deps} -> {dst_zstd_deps}")
  430. shutil.copyfile(self._zstd_deps, dst_zstd_deps)
  431. def _copy_mem(self):
  432. dst_mem = os.path.join(self._dst_lib, "common", "mem.h")
  433. self._log(f"Copying mem: {self._mem} -> {dst_mem}")
  434. shutil.copyfile(self._mem, dst_mem)
  435. def _hardwire_preprocessor(self, name: str, value: Optional[str] = None, undef=False):
  436. """
  437. If value=None then hardwire that it is defined, but not what the value is.
  438. If undef=True then value must be None.
  439. If value='' then the macro is defined to '' exactly.
  440. """
  441. assert not (undef and value is not None)
  442. for filepath in self._dst_lib_file_paths():
  443. file = FileLines(filepath)
  444. def _hardwire_defines(self):
  445. self._log("Hardwiring macros")
  446. partial_preprocessor = PartialPreprocessor(self._defs, self._replaces, self._undefs)
  447. for filepath in self._dst_lib_file_paths():
  448. partial_preprocessor.preprocess(filepath)
  449. def _remove_excludes(self):
  450. self._log("Removing excluded sections")
  451. for exclude in self._excludes:
  452. self._log(f"\tRemoving excluded sections for: {exclude}")
  453. begin_re = re.compile(f"BEGIN {exclude}")
  454. end_re = re.compile(f"END {exclude}")
  455. for filepath in self._dst_lib_file_paths():
  456. file = FileLines(filepath)
  457. outlines = []
  458. skipped = []
  459. emit = True
  460. for line in file.lines:
  461. if emit and begin_re.search(line) is not None:
  462. assert end_re.search(line) is None
  463. emit = False
  464. if emit:
  465. outlines.append(line)
  466. else:
  467. skipped.append(line)
  468. if end_re.search(line) is not None:
  469. assert begin_re.search(line) is None
  470. self._log(f"\t\tRemoving excluded section: {exclude}")
  471. for s in skipped:
  472. self._log(f"\t\t\t- {s}")
  473. emit = True
  474. skipped = []
  475. if not emit:
  476. raise RuntimeError("Excluded section unfinished!")
  477. file.lines = outlines
  478. file.write()
  479. def _rewrite_include(self, original, rewritten):
  480. self._log(f"\tRewriting include: {original} -> {rewritten}")
  481. regex = re.compile(f"\\s*#\\s*include\\s*(?P<include>{original})")
  482. for filepath in self._dst_lib_file_paths():
  483. file = FileLines(filepath)
  484. for i, line in enumerate(file.lines):
  485. match = regex.match(line)
  486. if match is None:
  487. continue
  488. s = match.start('include')
  489. e = match.end('include')
  490. file.lines[i] = line[:s] + rewritten + line[e:]
  491. file.write()
  492. def _rewrite_includes(self):
  493. self._log("Rewriting includes")
  494. for original, rewritten in self._rewritten_includes:
  495. self._rewrite_include(original, rewritten)
  496. def _replace_xxh64_prefix(self):
  497. if self._xxh64_prefix is None:
  498. return
  499. self._log(f"Replacing XXH64 prefix with {self._xxh64_prefix}")
  500. replacements = []
  501. if self._xxh64_state is not None:
  502. replacements.append(
  503. (re.compile(r"([^\w]|^)(?P<orig>XXH64_state_t)([^\w]|$)"), self._xxh64_state)
  504. )
  505. if self._xxh64_prefix is not None:
  506. replacements.append(
  507. (re.compile(r"([^\w]|^)(?P<orig>XXH64)[\(_]"), self._xxh64_prefix)
  508. )
  509. for filepath in self._dst_lib_file_paths():
  510. file = FileLines(filepath)
  511. for i, line in enumerate(file.lines):
  512. modified = False
  513. for regex, replacement in replacements:
  514. match = regex.search(line)
  515. while match is not None:
  516. modified = True
  517. b = match.start('orig')
  518. e = match.end('orig')
  519. line = line[:b] + replacement + line[e:]
  520. match = regex.search(line)
  521. if modified:
  522. self._log(f"\t- {file.lines[i][:-1]}")
  523. self._log(f"\t+ {line[:-1]}")
  524. file.lines[i] = line
  525. file.write()
  526. def _parse_sed(self, sed):
  527. assert sed[0] == 's'
  528. delim = sed[1]
  529. match = re.fullmatch(f's{delim}(.+){delim}(.*){delim}(.*)', sed)
  530. assert match is not None
  531. regex = re.compile(match.group(1))
  532. format_str = match.group(2)
  533. is_global = match.group(3) == 'g'
  534. return regex, format_str, is_global
  535. def _process_sed(self, sed):
  536. self._log(f"Processing sed: {sed}")
  537. regex, format_str, is_global = self._parse_sed(sed)
  538. for filepath in self._dst_lib_file_paths():
  539. file = FileLines(filepath)
  540. for i, line in enumerate(file.lines):
  541. modified = False
  542. while True:
  543. match = regex.search(line)
  544. if match is None:
  545. break
  546. replacement = format_str.format(match.groups(''), match.groupdict(''))
  547. b = match.start()
  548. e = match.end()
  549. line = line[:b] + replacement + line[e:]
  550. modified = True
  551. if not is_global:
  552. break
  553. if modified:
  554. self._log(f"\t- {file.lines[i][:-1]}")
  555. self._log(f"\t+ {line[:-1]}")
  556. file.lines[i] = line
  557. file.write()
  558. def _process_seds(self):
  559. self._log("Processing seds")
  560. for sed in self._seds:
  561. self._process_sed(sed)
  562. def go(self):
  563. self._copy_source_lib()
  564. self._copy_zstd_deps()
  565. self._copy_mem()
  566. self._hardwire_defines()
  567. self._remove_excludes()
  568. self._rewrite_includes()
  569. self._replace_xxh64_prefix()
  570. self._process_seds()
  571. def parse_optional_pair(defines: [str]) -> [(str, Optional[str])]:
  572. output = []
  573. for define in defines:
  574. parsed = define.split('=')
  575. if len(parsed) == 1:
  576. output.append((parsed[0], None))
  577. elif len(parsed) == 2:
  578. output.append((parsed[0], parsed[1]))
  579. else:
  580. raise RuntimeError(f"Bad define: {define}")
  581. return output
  582. def parse_pair(rewritten_includes: [str]) -> [(str, str)]:
  583. output = []
  584. for rewritten_include in rewritten_includes:
  585. parsed = rewritten_include.split('=')
  586. if len(parsed) == 2:
  587. output.append((parsed[0], parsed[1]))
  588. else:
  589. raise RuntimeError(f"Bad rewritten include: {rewritten_include}")
  590. return output
  591. def main(name, args):
  592. parser = argparse.ArgumentParser(prog=name)
  593. parser.add_argument("--zstd-deps", default="zstd_deps.h", help="Zstd dependencies file")
  594. parser.add_argument("--mem", default="mem.h", help="Memory module")
  595. parser.add_argument("--source-lib", default="../../lib", help="Location of the zstd library")
  596. parser.add_argument("--output-lib", default="./freestanding_lib", help="Where to output the freestanding zstd library")
  597. parser.add_argument("--xxhash", default=None, help="Alternate external xxhash include e.g. --xxhash='<xxhash.h>'. If set xxhash is not included.")
  598. parser.add_argument("--xxh64-state", default=None, help="Alternate XXH64 state type (excluding _) e.g. --xxh64-state='struct xxh64_state'")
  599. parser.add_argument("--xxh64-prefix", default=None, help="Alternate XXH64 function prefix (excluding _) e.g. --xxh64-prefix=xxh64")
  600. parser.add_argument("--rewrite-include", default=[], dest="rewritten_includes", action="append", help="Rewrite an include REGEX=NEW (e.g. '<stddef\\.h>=<linux/types.h>')")
  601. parser.add_argument("--sed", default=[], dest="seds", action="append", help="Apply a sed replacement. Format: `s/REGEX/FORMAT/[g]`. REGEX is a Python regex. FORMAT is a Python format string formatted by the regex dict.")
  602. parser.add_argument("-D", "--define", default=[], dest="defs", action="append", help="Pre-define this macro (can be passed multiple times)")
  603. parser.add_argument("-U", "--undefine", default=[], dest="undefs", action="append", help="Pre-undefine this macro (can be passed mutliple times)")
  604. parser.add_argument("-R", "--replace", default=[], dest="replaces", action="append", help="Pre-define this macro and replace the first ifndef block with its definition")
  605. parser.add_argument("-E", "--exclude", default=[], dest="excludes", action="append", help="Exclude all lines between 'BEGIN <EXCLUDE>' and 'END <EXCLUDE>'")
  606. args = parser.parse_args(args)
  607. # Always remove threading
  608. if "ZSTD_MULTITHREAD" not in args.undefs:
  609. args.undefs.append("ZSTD_MULTITHREAD")
  610. args.defs = parse_optional_pair(args.defs)
  611. for name, _ in args.defs:
  612. if name in args.undefs:
  613. raise RuntimeError(f"{name} is both defined and undefined!")
  614. # Always set tracing to 0
  615. if "ZSTD_NO_TRACE" not in (arg[0] for arg in args.defs):
  616. args.defs.append(("ZSTD_NO_TRACE", None))
  617. args.defs.append(("ZSTD_TRACE", "0"))
  618. args.replaces = parse_pair(args.replaces)
  619. for name, _ in args.replaces:
  620. if name in args.undefs or name in args.defs:
  621. raise RuntimeError(f"{name} is both replaced and (un)defined!")
  622. args.rewritten_includes = parse_pair(args.rewritten_includes)
  623. external_xxhash = False
  624. if args.xxhash is not None:
  625. external_xxhash = True
  626. args.rewritten_includes.append(('"(\\.\\./common/)?xxhash.h"', args.xxhash))
  627. if args.xxh64_prefix is not None:
  628. if not external_xxhash:
  629. raise RuntimeError("--xxh64-prefix may only be used with --xxhash provided")
  630. if args.xxh64_state is not None:
  631. if not external_xxhash:
  632. raise RuntimeError("--xxh64-state may only be used with --xxhash provided")
  633. Freestanding(
  634. args.zstd_deps,
  635. args.mem,
  636. args.source_lib,
  637. args.output_lib,
  638. external_xxhash,
  639. args.xxh64_state,
  640. args.xxh64_prefix,
  641. args.rewritten_includes,
  642. args.defs,
  643. args.replaces,
  644. args.undefs,
  645. args.excludes,
  646. args.seds,
  647. ).go()
  648. if __name__ == "__main__":
  649. main(sys.argv[0], sys.argv[1:])