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