| #!/usr/bin/env python3 |
| # Copyright (C) Microsoft Corporation. All rights reserved. |
| # This file is distributed under the University of Illinois Open Source License. |
| # See LICENSE.TXT for details. |
| """Embed a file's contents into a generated C++ source as a StringRef. |
| |
| Reads the input file as bytes and produces a C++ snippet of the form: |
| |
| llvm::StringRef Data = |
| "<escaped line 1>" |
| "<escaped line 2>" |
| ...; |
| |
| The generated file declares a single variable named ``Data`` at file |
| scope. The intent is for the file to be ``#include``-ed inside a wrapping |
| namespace by the consumer. |
| """ |
| |
| import argparse |
| import os |
| import sys |
| |
| |
| def escape_byte(b): |
| # Backslash and double-quote must always be escaped. |
| if b == 0x5C: |
| return "\\\\" |
| if b == 0x22: |
| return "\\\"" |
| if b == 0x09: |
| return "\\t" |
| if b == 0x0A: |
| return "\\n" |
| if b == 0x0D: |
| return "\\r" |
| # Printable ASCII passes through unchanged. |
| if 0x20 <= b < 0x7F: |
| return chr(b) |
| # Use 3-digit octal escapes for everything else. Octal escapes |
| # consume at most 3 digits, so they cannot accidentally absorb |
| # subsequent characters in the literal. |
| return "\\{:03o}".format(b) |
| |
| |
| def escape_line(line_bytes): |
| return "".join(escape_byte(b) for b in line_bytes) |
| |
| |
| def generate(input_path, output_path): |
| with open(input_path, "rb") as f: |
| data = f.read() |
| |
| # Split on '\n' but preserve the newline at the end of each piece. |
| pieces = [] |
| start = 0 |
| for i, b in enumerate(data): |
| if b == 0x0A: |
| pieces.append(data[start:i + 1]) |
| start = i + 1 |
| if start < len(data): |
| pieces.append(data[start:]) |
| if not pieces: |
| pieces = [b""] |
| |
| lines = [] |
| lines.append("// Generated by utils/embed_header.py. Do not edit.") |
| lines.append("// Source: {}".format(os.path.basename(input_path))) |
| lines.append("llvm::StringRef Data =") |
| for piece in pieces: |
| lines.append(" \"{}\"".format(escape_line(piece))) |
| # Terminate the variable declaration. |
| lines[-1] = lines[-1] + ";" |
| lines.append("") |
| |
| output_text = "\n".join(lines) |
| |
| # Avoid touching the file if the contents are unchanged so that |
| # downstream build steps are not invalidated unnecessarily. |
| if os.path.exists(output_path): |
| try: |
| with open(output_path, "r", encoding="utf-8") as f: |
| if f.read() == output_text: |
| return |
| except OSError: |
| pass |
| |
| out_dir = os.path.dirname(output_path) |
| if out_dir and not os.path.isdir(out_dir): |
| os.makedirs(out_dir, exist_ok=True) |
| |
| with open(output_path, "w", encoding="utf-8") as f: |
| f.write(output_text) |
| |
| |
| def main(argv): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument("input", help="Path to input file to embed.") |
| parser.add_argument("output", help="Path to generated C++ snippet.") |
| args = parser.parse_args(argv) |
| generate(args.input, args.output) |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |