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.h", 31 "compress/zstdmt_compress.h", 32 "compress/zstdmt_compress.c", 33] 34 35XXHASH_FILES = [ 36 "common/xxhash.c", 37 "common/xxhash.h", 38] 39 40 41class FileLines(object): 42 def __init__(self, filename): 43 self.filename = filename 44 with open(self.filename, "r") as f: 45 self.lines = f.readlines() 46 47 def write(self): 48 with open(self.filename, "w") as f: 49 f.write("".join(self.lines)) 50 51 52class PartialPreprocessor(object): 53 """ 54 Looks for simple ifdefs and ifndefs and replaces them. 55 Handles && and ||. 56 Has fancy logic to handle translating elifs to ifs. 57 Only looks for macros in the first part of the expression with no 58 parens. 59 Does not handle multi-line macros (only looks in first line). 60 """ 61 def __init__(self, defs: [(str, Optional[str])], replaces: [(str, str)], undefs: [str]): 62 MACRO_GROUP = r"(?P<macro>[a-zA-Z_][a-zA-Z_0-9]*)" 63 ELIF_GROUP = r"(?P<elif>el)?" 64 OP_GROUP = r"(?P<op>&&|\|\|)?" 65 66 self._defs = {macro:value for macro, value in defs} 67 self._replaces = {macro:value for macro, value in replaces} 68 self._defs.update(self._replaces) 69 self._undefs = set(undefs) 70 71 self._define = re.compile(r"\s*#\s*define") 72 self._if = re.compile(r"\s*#\s*if") 73 self._elif = re.compile(r"\s*#\s*(?P<elif>el)if") 74 self._else = re.compile(r"\s*#\s*(?P<else>else)") 75 self._endif = re.compile(r"\s*#\s*endif") 76 77 self._ifdef = re.compile(fr"\s*#\s*if(?P<not>n)?def {MACRO_GROUP}\s*") 78 self._if_defined = re.compile( 79 fr"\s*#\s*{ELIF_GROUP}if\s+(?P<not>!)?\s*defined\s*\(\s*{MACRO_GROUP}\s*\)\s*{OP_GROUP}" 80 ) 81 self._if_defined_value = re.compile( 82 fr"\s*#\s*{ELIF_GROUP}if\s+defined\s*\(\s*{MACRO_GROUP}\s*\)\s*" 83 fr"(?P<op>&&)\s*" 84 fr"(?P<openp>\()?\s*" 85 fr"(?P<macro2>[a-zA-Z_][a-zA-Z_0-9]*)\s*" 86 fr"(?P<cmp>[=><!]+)\s*" 87 fr"(?P<value>[0-9]*)\s*" 88 fr"(?P<closep>\))?\s*" 89 ) 90 self._if_true = re.compile( 91 fr"\s*#\s*{ELIF_GROUP}if\s+{MACRO_GROUP}\s*{OP_GROUP}" 92 ) 93 94 self._c_comment = re.compile(r"/\*.*?\*/") 95 self._cpp_comment = re.compile(r"//") 96 97 def _log(self, *args, **kwargs): 98 print(*args, **kwargs) 99 100 def _strip_comments(self, line): 101 # First strip c-style comments (may include //) 102 while True: 103 m = self._c_comment.search(line) 104 if m is None: 105 break 106 line = line[:m.start()] + line[m.end():] 107 108 # Then strip cpp-style comments 109 m = self._cpp_comment.search(line) 110 if m is not None: 111 line = line[:m.start()] 112 113 return line 114 115 def _fixup_indentation(self, macro, replace: [str]): 116 if len(replace) == 0: 117 return replace 118 if len(replace) == 1 and self._define.match(replace[0]) is None: 119 # If there is only one line, only replace defines 120 return replace 121 122 123 all_pound = True 124 for line in replace: 125 if not line.startswith('#'): 126 all_pound = False 127 if all_pound: 128 replace = [line[1:] for line in replace] 129 130 min_spaces = len(replace[0]) 131 for line in replace: 132 spaces = 0 133 for i, c in enumerate(line): 134 if c != ' ': 135 # Non-preprocessor line ==> skip the fixup 136 if not all_pound and c != '#': 137 return replace 138 spaces = i 139 break 140 min_spaces = min(min_spaces, spaces) 141 142 replace = [line[min_spaces:] for line in replace] 143 144 if all_pound: 145 replace = ["#" + line for line in replace] 146 147 return replace 148 149 def _handle_if_block(self, macro, idx, is_true, prepend): 150 """ 151 Remove the #if or #elif block starting on this line. 152 """ 153 REMOVE_ONE = 0 154 KEEP_ONE = 1 155 REMOVE_REST = 2 156 157 if is_true: 158 state = KEEP_ONE 159 else: 160 state = REMOVE_ONE 161 162 line = self._inlines[idx] 163 is_if = self._if.match(line) is not None 164 assert is_if or self._elif.match(line) is not None 165 depth = 0 166 167 start_idx = idx 168 169 idx += 1 170 replace = prepend 171 finished = False 172 while idx < len(self._inlines): 173 line = self._inlines[idx] 174 # Nested if statement 175 if self._if.match(line): 176 depth += 1 177 idx += 1 178 continue 179 # We're inside a nested statement 180 if depth > 0: 181 if self._endif.match(line): 182 depth -= 1 183 idx += 1 184 continue 185 186 # We're at the original depth 187 188 # Looking only for an endif. 189 # We've found a true statement, but haven't 190 # completely elided the if block, so we just 191 # remove the remainder. 192 if state == REMOVE_REST: 193 if self._endif.match(line): 194 if is_if: 195 # Remove the endif because we took the first if 196 idx += 1 197 finished = True 198 break 199 idx += 1 200 continue 201 202 if state == KEEP_ONE: 203 m = self._elif.match(line) 204 if self._endif.match(line): 205 replace += self._inlines[start_idx + 1:idx] 206 idx += 1 207 finished = True 208 break 209 if self._elif.match(line) or self._else.match(line): 210 replace += self._inlines[start_idx + 1:idx] 211 state = REMOVE_REST 212 idx += 1 213 continue 214 215 if state == REMOVE_ONE: 216 m = self._elif.match(line) 217 if m is not None: 218 if is_if: 219 idx += 1 220 b = m.start('elif') 221 e = m.end('elif') 222 assert e - b == 2 223 replace.append(line[:b] + line[e:]) 224 finished = True 225 break 226 m = self._else.match(line) 227 if m is not None: 228 if is_if: 229 idx += 1 230 while self._endif.match(self._inlines[idx]) is None: 231 replace.append(self._inlines[idx]) 232 idx += 1 233 idx += 1 234 finished = True 235 break 236 if self._endif.match(line): 237 if is_if: 238 # Remove the endif because no other elifs 239 idx += 1 240 finished = True 241 break 242 idx += 1 243 continue 244 if not finished: 245 raise RuntimeError("Unterminated if block!") 246 247 replace = self._fixup_indentation(macro, replace) 248 249 self._log(f"\tHardwiring {macro}") 250 if start_idx > 0: 251 self._log(f"\t\t {self._inlines[start_idx - 1][:-1]}") 252 for x in range(start_idx, idx): 253 self._log(f"\t\t- {self._inlines[x][:-1]}") 254 for line in replace: 255 self._log(f"\t\t+ {line[:-1]}") 256 if idx < len(self._inlines): 257 self._log(f"\t\t {self._inlines[idx][:-1]}") 258 259 return idx, replace 260 261 def _preprocess_once(self): 262 outlines = [] 263 idx = 0 264 changed = False 265 while idx < len(self._inlines): 266 line = self._inlines[idx] 267 sline = self._strip_comments(line) 268 m = self._ifdef.fullmatch(sline) 269 if_true = False 270 if m is None: 271 m = self._if_defined_value.fullmatch(sline) 272 if m is None: 273 m = self._if_defined.match(sline) 274 if m is None: 275 m = self._if_true.match(sline) 276 if_true = (m is not None) 277 if m is None: 278 outlines.append(line) 279 idx += 1 280 continue 281 282 groups = m.groupdict() 283 macro = groups['macro'] 284 op = groups.get('op') 285 286 if not (macro in self._defs or macro in self._undefs): 287 outlines.append(line) 288 idx += 1 289 continue 290 291 defined = macro in self._defs 292 293 # Needed variables set: 294 # resolved: Is the statement fully resolved? 295 # is_true: If resolved, is the statement true? 296 ifdef = False 297 if if_true: 298 if not defined: 299 outlines.append(line) 300 idx += 1 301 continue 302 303 defined_value = self._defs[macro] 304 is_int = True 305 try: 306 defined_value = int(defined_value) 307 except TypeError: 308 is_int = False 309 except ValueError: 310 is_int = False 311 312 resolved = is_int 313 is_true = (defined_value != 0) 314 315 if resolved and op is not None: 316 if op == '&&': 317 resolved = not is_true 318 else: 319 assert op == '||' 320 resolved = is_true 321 322 else: 323 ifdef = groups.get('not') is None 324 elseif = groups.get('elif') is not None 325 326 macro2 = groups.get('macro2') 327 cmp = groups.get('cmp') 328 value = groups.get('value') 329 openp = groups.get('openp') 330 closep = groups.get('closep') 331 332 is_true = (ifdef == defined) 333 resolved = True 334 if op is not None: 335 if op == '&&': 336 resolved = not is_true 337 else: 338 assert op == '||' 339 resolved = is_true 340 341 if macro2 is not None and not resolved: 342 assert ifdef and defined and op == '&&' and cmp is not None 343 # If the statment is true, but we have a single value check, then 344 # check the value. 345 defined_value = self._defs[macro] 346 are_ints = True 347 try: 348 defined_value = int(defined_value) 349 value = int(value) 350 except TypeError: 351 are_ints = False 352 except ValueError: 353 are_ints = False 354 if ( 355 macro == macro2 and 356 ((openp is None) == (closep is None)) and 357 are_ints 358 ): 359 resolved = True 360 if cmp == '<': 361 is_true = defined_value < value 362 elif cmp == '<=': 363 is_true = defined_value <= value 364 elif cmp == '==': 365 is_true = defined_value == value 366 elif cmp == '!=': 367 is_true = defined_value != value 368 elif cmp == '>=': 369 is_true = defined_value >= value 370 elif cmp == '>': 371 is_true = defined_value > value 372 else: 373 resolved = False 374 375 if op is not None and not resolved: 376 # Remove the first op in the line + spaces 377 if op == '&&': 378 opre = op 379 else: 380 assert op == '||' 381 opre = r'\|\|' 382 needle = re.compile(fr"(?P<if>\s*#\s*(el)?if\s+).*?(?P<op>{opre}\s*)") 383 match = needle.match(line) 384 assert match is not None 385 newline = line[:match.end('if')] + line[match.end('op'):] 386 387 self._log(f"\tHardwiring partially resolved {macro}") 388 self._log(f"\t\t- {line[:-1]}") 389 self._log(f"\t\t+ {newline[:-1]}") 390 391 outlines.append(newline) 392 idx += 1 393 continue 394 395 # Skip any statements we cannot fully compute 396 if not resolved: 397 outlines.append(line) 398 idx += 1 399 continue 400 401 prepend = [] 402 if macro in self._replaces: 403 assert not ifdef 404 assert op is None 405 value = self._replaces.pop(macro) 406 prepend = [f"#define {macro} {value}\n"] 407 408 idx, replace = self._handle_if_block(macro, idx, is_true, prepend) 409 outlines += replace 410 changed = True 411 412 return changed, outlines 413 414 def preprocess(self, filename): 415 with open(filename, 'r') as f: 416 self._inlines = f.readlines() 417 changed = True 418 iters = 0 419 while changed: 420 iters += 1 421 changed, outlines = self._preprocess_once() 422 self._inlines = outlines 423 424 with open(filename, 'w') as f: 425 f.write(''.join(self._inlines)) 426 427 428class Freestanding(object): 429 def __init__( 430 self, zstd_deps: str, mem: str, source_lib: str, output_lib: str, 431 external_xxhash: bool, xxh64_state: Optional[str], 432 xxh64_prefix: Optional[str], rewritten_includes: [(str, str)], 433 defs: [(str, Optional[str])], replaces: [(str, str)], 434 undefs: [str], excludes: [str], seds: [str], 435 ): 436 self._zstd_deps = zstd_deps 437 self._mem = mem 438 self._src_lib = source_lib 439 self._dst_lib = output_lib 440 self._external_xxhash = external_xxhash 441 self._xxh64_state = xxh64_state 442 self._xxh64_prefix = xxh64_prefix 443 self._rewritten_includes = rewritten_includes 444 self._defs = defs 445 self._replaces = replaces 446 self._undefs = undefs 447 self._excludes = excludes 448 self._seds = seds 449 450 def _dst_lib_file_paths(self): 451 """ 452 Yields all the file paths in the dst_lib. 453 """ 454 for root, dirname, filenames in os.walk(self._dst_lib): 455 for filename in filenames: 456 filepath = os.path.join(root, filename) 457 yield filepath 458 459 def _log(self, *args, **kwargs): 460 print(*args, **kwargs) 461 462 def _copy_file(self, lib_path): 463 suffixes = [".c", ".h", ".S"] 464 if not any((lib_path.endswith(suffix) for suffix in suffixes)): 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