1#!/usr/bin/env python3 2# ################################################################ 3# Copyright (c) Meta Platforms, Inc. and affiliates. 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 statement 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], spdx: bool, 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 self._spdx = spdx 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 suffixes = [".c", ".h", ".S"] 465 if not any((lib_path.endswith(suffix) for suffix in suffixes)): 466 return 467 if lib_path in SKIPPED_FILES: 468 self._log(f"\tSkipping file: {lib_path}") 469 return 470 if self._external_xxhash and lib_path in XXHASH_FILES: 471 self._log(f"\tSkipping xxhash file: {lib_path}") 472 return 473 474 src_path = os.path.join(self._src_lib, lib_path) 475 dst_path = os.path.join(self._dst_lib, lib_path) 476 self._log(f"\tCopying: {src_path} -> {dst_path}") 477 shutil.copyfile(src_path, dst_path) 478 479 def _copy_source_lib(self): 480 self._log("Copying source library into output library") 481 482 assert os.path.exists(self._src_lib) 483 os.makedirs(self._dst_lib, exist_ok=True) 484 self._copy_file("zstd.h") 485 self._copy_file("zstd_errors.h") 486 for subdir in INCLUDED_SUBDIRS: 487 src_dir = os.path.join(self._src_lib, subdir) 488 dst_dir = os.path.join(self._dst_lib, subdir) 489 490 assert os.path.exists(src_dir) 491 os.makedirs(dst_dir, exist_ok=True) 492 493 for filename in os.listdir(src_dir): 494 lib_path = os.path.join(subdir, filename) 495 self._copy_file(lib_path) 496 497 def _copy_zstd_deps(self): 498 dst_zstd_deps = os.path.join(self._dst_lib, "common", "zstd_deps.h") 499 self._log(f"Copying zstd_deps: {self._zstd_deps} -> {dst_zstd_deps}") 500 shutil.copyfile(self._zstd_deps, dst_zstd_deps) 501 502 def _copy_mem(self): 503 dst_mem = os.path.join(self._dst_lib, "common", "mem.h") 504 self._log(f"Copying mem: {self._mem} -> {dst_mem}") 505 shutil.copyfile(self._mem, dst_mem) 506 507 def _hardwire_preprocessor(self, name: str, value: Optional[str] = None, undef=False): 508 """ 509 If value=None then hardwire that it is defined, but not what the value is. 510 If undef=True then value must be None. 511 If value='' then the macro is defined to '' exactly. 512 """ 513 assert not (undef and value is not None) 514 for filepath in self._dst_lib_file_paths(): 515 file = FileLines(filepath) 516 517 def _hardwire_defines(self): 518 self._log("Hardwiring macros") 519 partial_preprocessor = PartialPreprocessor(self._defs, self._replaces, self._undefs) 520 for filepath in self._dst_lib_file_paths(): 521 partial_preprocessor.preprocess(filepath) 522 523 def _remove_excludes(self): 524 self._log("Removing excluded sections") 525 for exclude in self._excludes: 526 self._log(f"\tRemoving excluded sections for: {exclude}") 527 begin_re = re.compile(f"BEGIN {exclude}") 528 end_re = re.compile(f"END {exclude}") 529 for filepath in self._dst_lib_file_paths(): 530 file = FileLines(filepath) 531 outlines = [] 532 skipped = [] 533 emit = True 534 for line in file.lines: 535 if emit and begin_re.search(line) is not None: 536 assert end_re.search(line) is None 537 emit = False 538 if emit: 539 outlines.append(line) 540 else: 541 skipped.append(line) 542 if end_re.search(line) is not None: 543 assert begin_re.search(line) is None 544 self._log(f"\t\tRemoving excluded section: {exclude}") 545 for s in skipped: 546 self._log(f"\t\t\t- {s}") 547 emit = True 548 skipped = [] 549 if not emit: 550 raise RuntimeError("Excluded section unfinished!") 551 file.lines = outlines 552 file.write() 553 554 def _rewrite_include(self, original, rewritten): 555 self._log(f"\tRewriting include: {original} -> {rewritten}") 556 regex = re.compile(f"\\s*#\\s*include\\s*(?P<include>{original})") 557 for filepath in self._dst_lib_file_paths(): 558 file = FileLines(filepath) 559 for i, line in enumerate(file.lines): 560 match = regex.match(line) 561 if match is None: 562 continue 563 s = match.start('include') 564 e = match.end('include') 565 file.lines[i] = line[:s] + rewritten + line[e:] 566 file.write() 567 568 def _rewrite_includes(self): 569 self._log("Rewriting includes") 570 for original, rewritten in self._rewritten_includes: 571 self._rewrite_include(original, rewritten) 572 573 def _replace_xxh64_prefix(self): 574 if self._xxh64_prefix is None: 575 return 576 self._log(f"Replacing XXH64 prefix with {self._xxh64_prefix}") 577 replacements = [] 578 if self._xxh64_state is not None: 579 replacements.append( 580 (re.compile(r"([^\w]|^)(?P<orig>XXH64_state_t)([^\w]|$)"), self._xxh64_state) 581 ) 582 if self._xxh64_prefix is not None: 583 replacements.append( 584 (re.compile(r"([^\w]|^)(?P<orig>XXH64)[\(_]"), self._xxh64_prefix) 585 ) 586 for filepath in self._dst_lib_file_paths(): 587 file = FileLines(filepath) 588 for i, line in enumerate(file.lines): 589 modified = False 590 for regex, replacement in replacements: 591 match = regex.search(line) 592 while match is not None: 593 modified = True 594 b = match.start('orig') 595 e = match.end('orig') 596 line = line[:b] + replacement + line[e:] 597 match = regex.search(line) 598 if modified: 599 self._log(f"\t- {file.lines[i][:-1]}") 600 self._log(f"\t+ {line[:-1]}") 601 file.lines[i] = line 602 file.write() 603 604 def _parse_sed(self, sed): 605 assert sed[0] == 's' 606 delim = sed[1] 607 match = re.fullmatch(f's{delim}(.+){delim}(.*){delim}(.*)', sed) 608 assert match is not None 609 regex = re.compile(match.group(1)) 610 format_str = match.group(2) 611 is_global = match.group(3) == 'g' 612 return regex, format_str, is_global 613 614 def _process_sed(self, sed): 615 self._log(f"Processing sed: {sed}") 616 regex, format_str, is_global = self._parse_sed(sed) 617 618 for filepath in self._dst_lib_file_paths(): 619 file = FileLines(filepath) 620 for i, line in enumerate(file.lines): 621 modified = False 622 while True: 623 match = regex.search(line) 624 if match is None: 625 break 626 replacement = format_str.format(match.groups(''), match.groupdict('')) 627 b = match.start() 628 e = match.end() 629 line = line[:b] + replacement + line[e:] 630 modified = True 631 if not is_global: 632 break 633 if modified: 634 self._log(f"\t- {file.lines[i][:-1]}") 635 self._log(f"\t+ {line[:-1]}") 636 file.lines[i] = line 637 file.write() 638 639 def _process_seds(self): 640 self._log("Processing seds") 641 for sed in self._seds: 642 self._process_sed(sed) 643 644 def _process_spdx(self): 645 if not self._spdx: 646 return 647 self._log("Processing spdx") 648 SPDX_C = "// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause\n" 649 SPDX_H_S = "/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */\n" 650 for filepath in self._dst_lib_file_paths(): 651 file = FileLines(filepath) 652 if file.lines[0] == SPDX_C or file.lines[0] == SPDX_H_S: 653 continue 654 for line in file.lines: 655 if "SPDX-License-Identifier" in line: 656 raise RuntimeError(f"Unexpected SPDX license identifier: {file.filename} {repr(line)}") 657 if file.filename.endswith(".c"): 658 file.lines.insert(0, SPDX_C) 659 elif file.filename.endswith(".h") or file.filename.endswith(".S"): 660 file.lines.insert(0, SPDX_H_S) 661 else: 662 raise RuntimeError(f"Unexpected file extension: {file.filename}") 663 file.write() 664 665 666 667 def go(self): 668 self._copy_source_lib() 669 self._copy_zstd_deps() 670 self._copy_mem() 671 self._hardwire_defines() 672 self._remove_excludes() 673 self._rewrite_includes() 674 self._replace_xxh64_prefix() 675 self._process_seds() 676 self._process_spdx() 677 678 679def parse_optional_pair(defines: [str]) -> [(str, Optional[str])]: 680 output = [] 681 for define in defines: 682 parsed = define.split('=') 683 if len(parsed) == 1: 684 output.append((parsed[0], None)) 685 elif len(parsed) == 2: 686 output.append((parsed[0], parsed[1])) 687 else: 688 raise RuntimeError(f"Bad define: {define}") 689 return output 690 691 692def parse_pair(rewritten_includes: [str]) -> [(str, str)]: 693 output = [] 694 for rewritten_include in rewritten_includes: 695 parsed = rewritten_include.split('=') 696 if len(parsed) == 2: 697 output.append((parsed[0], parsed[1])) 698 else: 699 raise RuntimeError(f"Bad rewritten include: {rewritten_include}") 700 return output 701 702 703 704def main(name, args): 705 parser = argparse.ArgumentParser(prog=name) 706 parser.add_argument("--zstd-deps", default="zstd_deps.h", help="Zstd dependencies file") 707 parser.add_argument("--mem", default="mem.h", help="Memory module") 708 parser.add_argument("--source-lib", default="../../lib", help="Location of the zstd library") 709 parser.add_argument("--output-lib", default="./freestanding_lib", help="Where to output the freestanding zstd library") 710 parser.add_argument("--xxhash", default=None, help="Alternate external xxhash include e.g. --xxhash='<xxhash.h>'. If set xxhash is not included.") 711 parser.add_argument("--xxh64-state", default=None, help="Alternate XXH64 state type (excluding _) e.g. --xxh64-state='struct xxh64_state'") 712 parser.add_argument("--xxh64-prefix", default=None, help="Alternate XXH64 function prefix (excluding _) e.g. --xxh64-prefix=xxh64") 713 parser.add_argument("--rewrite-include", default=[], dest="rewritten_includes", action="append", help="Rewrite an include REGEX=NEW (e.g. '<stddef\\.h>=<linux/types.h>')") 714 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.") 715 parser.add_argument("--spdx", action="store_true", help="Add SPDX License Identifiers") 716 parser.add_argument("-D", "--define", default=[], dest="defs", action="append", help="Pre-define this macro (can be passed multiple times)") 717 parser.add_argument("-U", "--undefine", default=[], dest="undefs", action="append", help="Pre-undefine this macro (can be passed multiple times)") 718 parser.add_argument("-R", "--replace", default=[], dest="replaces", action="append", help="Pre-define this macro and replace the first ifndef block with its definition") 719 parser.add_argument("-E", "--exclude", default=[], dest="excludes", action="append", help="Exclude all lines between 'BEGIN <EXCLUDE>' and 'END <EXCLUDE>'") 720 args = parser.parse_args(args) 721 722 # Always remove threading 723 if "ZSTD_MULTITHREAD" not in args.undefs: 724 args.undefs.append("ZSTD_MULTITHREAD") 725 726 args.defs = parse_optional_pair(args.defs) 727 for name, _ in args.defs: 728 if name in args.undefs: 729 raise RuntimeError(f"{name} is both defined and undefined!") 730 731 # Always set tracing to 0 732 if "ZSTD_NO_TRACE" not in (arg[0] for arg in args.defs): 733 args.defs.append(("ZSTD_NO_TRACE", None)) 734 args.defs.append(("ZSTD_TRACE", "0")) 735 736 args.replaces = parse_pair(args.replaces) 737 for name, _ in args.replaces: 738 if name in args.undefs or name in args.defs: 739 raise RuntimeError(f"{name} is both replaced and (un)defined!") 740 741 args.rewritten_includes = parse_pair(args.rewritten_includes) 742 743 external_xxhash = False 744 if args.xxhash is not None: 745 external_xxhash = True 746 args.rewritten_includes.append(('"(\\.\\./common/)?xxhash.h"', args.xxhash)) 747 748 if args.xxh64_prefix is not None: 749 if not external_xxhash: 750 raise RuntimeError("--xxh64-prefix may only be used with --xxhash provided") 751 752 if args.xxh64_state is not None: 753 if not external_xxhash: 754 raise RuntimeError("--xxh64-state may only be used with --xxhash provided") 755 756 Freestanding( 757 args.zstd_deps, 758 args.mem, 759 args.source_lib, 760 args.output_lib, 761 external_xxhash, 762 args.xxh64_state, 763 args.xxh64_prefix, 764 args.rewritten_includes, 765 args.defs, 766 args.replaces, 767 args.undefs, 768 args.excludes, 769 args.seds, 770 args.spdx, 771 ).go() 772 773if __name__ == "__main__": 774 main(sys.argv[0], sys.argv[1:]) 775