blob: 07ad203f19473dcd83429483756eee70564496d8 [file] [edit]
#!/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:]))