diff options
Diffstat (limited to 'third_party/libwebrtc/build/toolchain/win/rc/rc.py')
-rwxr-xr-x | third_party/libwebrtc/build/toolchain/win/rc/rc.py | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/third_party/libwebrtc/build/toolchain/win/rc/rc.py b/third_party/libwebrtc/build/toolchain/win/rc/rc.py new file mode 100755 index 0000000000..2ab41225fb --- /dev/null +++ b/third_party/libwebrtc/build/toolchain/win/rc/rc.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""usage: rc.py [options] input.res +A resource compiler for .rc files. + +options: +-h, --help Print this message. +-I<dir> Add include path, used for both headers and resources. +-imsvc<dir> Add system include path, used for preprocessing only. +/winsysroot<d> Set winsysroot, used for preprocessing only. +-D<sym> Define a macro for the preprocessor. +/fo<out> Set path of output .res file. +/nologo Ignored (rc.py doesn't print a logo by default). +/showIncludes Print referenced header and resource files.""" + +from __future__ import print_function +from collections import namedtuple +import codecs +import os +import re +import subprocess +import sys +import tempfile + + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) +SRC_DIR = \ + os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(THIS_DIR)))) + + +def ParseFlags(): + """Parses flags off sys.argv and returns the parsed flags.""" + # Can't use optparse / argparse because of /fo flag :-/ + includes = [] + imsvcs = [] + winsysroot = [] + defines = [] + output = None + input = None + show_includes = False + # Parse. + for flag in sys.argv[1:]: + if flag == '-h' or flag == '--help': + print(__doc__) + sys.exit(0) + if flag.startswith('-I'): + includes.append(flag) + elif flag.startswith('-imsvc'): + imsvcs.append(flag) + elif flag.startswith('/winsysroot'): + winsysroot = [flag] + elif flag.startswith('-D'): + defines.append(flag) + elif flag.startswith('/fo'): + if output: + print('rc.py: error: multiple /fo flags', '/fo' + output, flag, + file=sys.stderr) + sys.exit(1) + output = flag[3:] + elif flag == '/nologo': + pass + elif flag == '/showIncludes': + show_includes = True + elif (flag.startswith('-') or + (flag.startswith('/') and not os.path.exists(flag))): + print('rc.py: error: unknown flag', flag, file=sys.stderr) + print(__doc__, file=sys.stderr) + sys.exit(1) + else: + if input: + print('rc.py: error: multiple inputs:', input, flag, file=sys.stderr) + sys.exit(1) + input = flag + # Validate and set default values. + if not input: + print('rc.py: error: no input file', file=sys.stderr) + sys.exit(1) + if not output: + output = os.path.splitext(input)[0] + '.res' + Flags = namedtuple('Flags', [ + 'includes', 'defines', 'output', 'imsvcs', 'winsysroot', 'input', + 'show_includes' + ]) + return Flags(includes=includes, + defines=defines, + output=output, + imsvcs=imsvcs, + winsysroot=winsysroot, + input=input, + show_includes=show_includes) + + +def ReadInput(input): + """"Reads input and returns it. For UTF-16LEBOM input, converts to UTF-8.""" + # Microsoft's rc.exe only supports unicode in the form of UTF-16LE with a BOM. + # Our rc binary sniffs for UTF-16LE. If that's not found, if /utf-8 is + # passed, the input is treated as UTF-8. If /utf-8 is not passed and the + # input is not UTF-16LE, then our rc errors out on characters outside of + # 7-bit ASCII. Since the driver always converts UTF-16LE to UTF-8 here (for + # the preprocessor, which doesn't support UTF-16LE), our rc will either see + # UTF-8 with the /utf-8 flag (for UTF-16LE input), or ASCII input. + # This is compatible with Microsoft rc.exe. If we wanted, we could expose + # a /utf-8 flag for the driver for UTF-8 .rc inputs too. + # TODO(thakis): Microsoft's rc.exe supports BOM-less UTF-16LE. We currently + # don't, but for chrome it currently doesn't matter. + is_utf8 = False + try: + with open(input, 'rb') as rc_file: + rc_file_data = rc_file.read() + if rc_file_data.startswith(codecs.BOM_UTF16_LE): + rc_file_data = rc_file_data[2:].decode('utf-16le').encode('utf-8') + is_utf8 = True + except IOError: + print('rc.py: failed to open', input, file=sys.stderr) + sys.exit(1) + except UnicodeDecodeError: + print('rc.py: failed to decode UTF-16 despite BOM', input, file=sys.stderr) + sys.exit(1) + return rc_file_data, is_utf8 + + +def Preprocess(rc_file_data, flags): + """Runs the input file through the preprocessor.""" + clang = os.path.join(SRC_DIR, 'third_party', 'llvm-build', + 'Release+Asserts', 'bin', 'clang-cl') + # Let preprocessor write to a temp file so that it doesn't interfere + # with /showIncludes output on stdout. + if sys.platform == 'win32': + clang += '.exe' + temp_handle, temp_file = tempfile.mkstemp(suffix='.i') + # Closing temp_handle immediately defeats the purpose of mkstemp(), but I + # can't figure out how to let write to the temp file on Windows otherwise. + os.close(temp_handle) + clang_cmd = [clang, '/P', '/DRC_INVOKED', '/TC', '-', '/Fi' + temp_file] + if flags.imsvcs: + clang_cmd += ['/X'] + if os.path.dirname(flags.input): + # This must precede flags.includes. + clang_cmd.append('-I' + os.path.dirname(flags.input)) + if flags.show_includes: + clang_cmd.append('/showIncludes') + clang_cmd += flags.imsvcs + flags.winsysroot + flags.includes + flags.defines + p = subprocess.Popen(clang_cmd, stdin=subprocess.PIPE) + p.communicate(input=rc_file_data) + if p.returncode != 0: + sys.exit(p.returncode) + preprocessed_output = open(temp_file, 'rb').read() + os.remove(temp_file) + + # rc.exe has a wacko preprocessor: + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx + # """RC treats files with the .c and .h extensions in a special manner. It + # assumes that a file with one of these extensions does not contain + # resources. If a file has the .c or .h file name extension, RC ignores all + # lines in the file except the preprocessor directives.""" + # Thankfully, the Microsoft headers are mostly good about putting everything + # in the system headers behind `if !defined(RC_INVOKED)`, so regular + # preprocessing with RC_INVOKED defined works. + return preprocessed_output + + +def RunRc(preprocessed_output, is_utf8, flags): + if sys.platform.startswith('linux'): + rc = os.path.join(THIS_DIR, 'linux64', 'rc') + elif sys.platform == 'darwin': + rc = os.path.join(THIS_DIR, 'mac', 'rc') + elif sys.platform == 'win32': + rc = os.path.join(THIS_DIR, 'win', 'rc.exe') + else: + print('rc.py: error: unsupported platform', sys.platform, file=sys.stderr) + sys.exit(1) + rc_cmd = [rc] + # Make sure rc-relative resources can be found: + if os.path.dirname(flags.input): + rc_cmd.append('/cd' + os.path.dirname(flags.input)) + rc_cmd.append('/fo' + flags.output) + if is_utf8: + rc_cmd.append('/utf-8') + # TODO(thakis): cl currently always prints full paths for /showIncludes, + # but clang-cl /P doesn't. Which one is right? + if flags.show_includes: + rc_cmd.append('/showIncludes') + # Microsoft rc.exe searches for referenced files relative to -I flags in + # addition to the pwd, so -I flags need to be passed both to both + # the preprocessor and rc. + rc_cmd += flags.includes + p = subprocess.Popen(rc_cmd, stdin=subprocess.PIPE) + p.communicate(input=preprocessed_output) + + if flags.show_includes and p.returncode == 0: + TOOL_DIR = os.path.dirname(os.path.relpath(THIS_DIR)).replace("\\", "/") + # Since tool("rc") can't have deps, add deps on this script and on rc.py + # and its deps here, so that rc edges become dirty if rc.py changes. + print('Note: including file: {}/tool_wrapper.py'.format(TOOL_DIR)) + print('Note: including file: {}/rc/rc.py'.format(TOOL_DIR)) + print( + 'Note: including file: {}/rc/linux64/rc.sha1'.format(TOOL_DIR)) + print('Note: including file: {}/rc/mac/rc.sha1'.format(TOOL_DIR)) + print( + 'Note: including file: {}/rc/win/rc.exe.sha1'.format(TOOL_DIR)) + + return p.returncode + + +def CompareToMsRcOutput(preprocessed_output, is_utf8, flags): + msrc_in = flags.output + '.preprocessed.rc' + + # Strip preprocessor line markers. + preprocessed_output = re.sub(br'^#.*$', b'', preprocessed_output, flags=re.M) + if is_utf8: + preprocessed_output = preprocessed_output.decode('utf-8').encode('utf-16le') + with open(msrc_in, 'wb') as f: + f.write(preprocessed_output) + + msrc_out = flags.output + '_ms_rc' + msrc_cmd = ['rc', '/nologo', '/x', '/fo' + msrc_out] + + # Make sure rc-relative resources can be found. rc.exe looks for external + # resource files next to the file, but the preprocessed file isn't where the + # input was. + # Note that rc searches external resource files in the order of + # 1. next to the input file + # 2. relative to cwd + # 3. next to -I directories + # Changing the cwd means we'd have to rewrite all -I flags, so just add + # the input file dir as -I flag. That technically gets the order of 1 and 2 + # wrong, but in Chromium's build the cwd is the gn out dir, and generated + # files there are in obj/ and gen/, so this difference doesn't matter in + # practice. + if os.path.dirname(flags.input): + msrc_cmd += [ '-I' + os.path.dirname(flags.input) ] + + # Microsoft rc.exe searches for referenced files relative to -I flags in + # addition to the pwd, so -I flags need to be passed both to both + # the preprocessor and rc. + msrc_cmd += flags.includes + + # Input must come last. + msrc_cmd += [ msrc_in ] + + rc_exe_exit_code = subprocess.call(msrc_cmd) + # Assert Microsoft rc.exe and rc.py produced identical .res files. + if rc_exe_exit_code == 0: + import filecmp + assert filecmp.cmp(msrc_out, flags.output) + return rc_exe_exit_code + + +def main(): + # This driver has to do these things: + # 1. Parse flags. + # 2. Convert the input from UTF-16LE to UTF-8 if needed. + # 3. Pass the input through a preprocessor (and clean up the preprocessor's + # output in minor ways). + # 4. Call rc for the heavy lifting. + flags = ParseFlags() + rc_file_data, is_utf8 = ReadInput(flags.input) + preprocessed_output = Preprocess(rc_file_data, flags) + rc_exe_exit_code = RunRc(preprocessed_output, is_utf8, flags) + + # 5. On Windows, we also call Microsoft's rc.exe and check that we produced + # the same output. + # Since Microsoft's rc has a preprocessor that only accepts 32 characters + # for macro names, feed the clang-preprocessed source into it instead + # of using ms rc's preprocessor. + if sys.platform == 'win32' and rc_exe_exit_code == 0: + rc_exe_exit_code = CompareToMsRcOutput(preprocessed_output, is_utf8, flags) + + return rc_exe_exit_code + + +if __name__ == '__main__': + sys.exit(main()) |