• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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