tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

rc.py (10729B)


      1 #!/usr/bin/env python3
      2 # Copyright 2017 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """usage: rc.py [options] input.res
      7 A resource compiler for .rc files.
      8 
      9 options:
     10 -h, --help     Print this message.
     11 -Werror        Treat warnings as errors.
     12 -I<dir>        Add include path, used for both headers and resources.
     13 -imsvc<dir>    Add system include path, used for preprocessing only.
     14 /winsysroot<d> Set winsysroot, used for preprocessing only.
     15 -D<sym>        Define a macro for the preprocessor.
     16 /fo<out>       Set path of output .res file.
     17 /nologo        Ignored (rc.py doesn't print a logo by default).
     18 /showIncludes  Print referenced header and resource files."""
     19 
     20 from collections import namedtuple
     21 import codecs
     22 import os
     23 import re
     24 import subprocess
     25 import sys
     26 import tempfile
     27 
     28 
     29 THIS_DIR = os.path.abspath(os.path.dirname(__file__))
     30 SRC_DIR = \
     31    os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(THIS_DIR))))
     32 
     33 
     34 def ParseFlags():
     35  """Parses flags off sys.argv and returns the parsed flags."""
     36  # Can't use optparse / argparse because of /fo flag :-/
     37  includes = []
     38  imsvcs = []
     39  winsysroot = []
     40  defines = []
     41  output = None
     42  input = None
     43  show_includes = False
     44  werror = False
     45  # Parse.
     46  for flag in sys.argv[1:]:
     47    if flag == '-h' or flag == '--help':
     48      print(__doc__)
     49      sys.exit(0)
     50    if flag.startswith('-I'):
     51      includes.append(flag)
     52    elif flag.startswith('-imsvc'):
     53      imsvcs.append(flag)
     54    elif flag.startswith('/winsysroot'):
     55      winsysroot = [flag]
     56    elif flag.startswith('-D'):
     57      defines.append(flag)
     58    elif flag.startswith('/fo'):
     59      if output:
     60        print('rc.py: error: multiple /fo flags', '/fo' + output, flag,
     61              file=sys.stderr)
     62        sys.exit(1)
     63      output = flag[3:]
     64    elif flag == '/nologo':
     65      pass
     66    elif flag == '/showIncludes':
     67      show_includes = True
     68    elif flag == '-Werror':
     69      werror = True
     70    elif (flag.startswith('-') or
     71          (flag.startswith('/') and not os.path.exists(flag))):
     72      print('rc.py: error: unknown flag', flag, file=sys.stderr)
     73      print(__doc__, file=sys.stderr)
     74      sys.exit(1)
     75    else:
     76      if input:
     77        print('rc.py: error: multiple inputs:', input, flag, file=sys.stderr)
     78        sys.exit(1)
     79      input = flag
     80  # Validate and set default values.
     81  if not input:
     82    print('rc.py: error: no input file', file=sys.stderr)
     83    sys.exit(1)
     84  if not output:
     85    output = os.path.splitext(input)[0] + '.res'
     86  Flags = namedtuple('Flags', [
     87      'includes', 'defines', 'output', 'imsvcs', 'winsysroot', 'input',
     88      'show_includes', 'werror'
     89  ])
     90  return Flags(includes=includes,
     91               defines=defines,
     92               output=output,
     93               imsvcs=imsvcs,
     94               winsysroot=winsysroot,
     95               input=input,
     96               show_includes=show_includes,
     97               werror=werror)
     98 
     99 
    100 def ReadInput(input):
    101  """"Reads input and returns it. For UTF-16LEBOM input, converts to UTF-8."""
    102  # Microsoft's rc.exe only supports unicode in the form of UTF-16LE with a BOM.
    103  # Our rc binary sniffs for UTF-16LE.  If that's not found, if /utf-8 is
    104  # passed, the input is treated as UTF-8.  If /utf-8 is not passed and the
    105  # input is not UTF-16LE, then our rc errors out on characters outside of
    106  # 7-bit ASCII.  Since the driver always converts UTF-16LE to UTF-8 here (for
    107  # the preprocessor, which doesn't support UTF-16LE), our rc will either see
    108  # UTF-8 with the /utf-8 flag (for UTF-16LE input), or ASCII input.
    109  # This is compatible with Microsoft rc.exe.  If we wanted, we could expose
    110  # a /utf-8 flag for the driver for UTF-8 .rc inputs too.
    111  # TODO(thakis): Microsoft's rc.exe supports BOM-less UTF-16LE. We currently
    112  # don't, but for chrome it currently doesn't matter.
    113  is_utf8 = False
    114  try:
    115    with open(input, 'rb') as rc_file:
    116      rc_file_data = rc_file.read()
    117      if rc_file_data.startswith(codecs.BOM_UTF16_LE):
    118        rc_file_data = rc_file_data[2:].decode('utf-16le').encode('utf-8')
    119        is_utf8 = True
    120  except IOError:
    121    print('rc.py: failed to open', input, file=sys.stderr)
    122    sys.exit(1)
    123  except UnicodeDecodeError:
    124    print('rc.py: failed to decode UTF-16 despite BOM', input, file=sys.stderr)
    125    sys.exit(1)
    126  return rc_file_data, is_utf8
    127 
    128 
    129 def Preprocess(rc_file_data, flags):
    130  """Runs the input file through the preprocessor."""
    131  clang = os.path.join(SRC_DIR, 'third_party', 'llvm-build',
    132                       'Release+Asserts', 'bin', 'clang-cl')
    133  # Let preprocessor write to a temp file so that it doesn't interfere
    134  # with /showIncludes output on stdout.
    135  if sys.platform == 'win32':
    136    clang += '.exe'
    137  temp_handle, temp_file = tempfile.mkstemp(suffix='.i')
    138  # Closing temp_handle immediately defeats the purpose of mkstemp(), but I
    139  # can't figure out how to let write to the temp file on Windows otherwise.
    140  os.close(temp_handle)
    141  clang_cmd = [clang, '/P', '/DRC_INVOKED', '/TC', '-', '/Fi' + temp_file]
    142  if flags.imsvcs:
    143    clang_cmd += ['/X']
    144  if os.path.dirname(flags.input):
    145    # This must precede flags.includes.
    146    clang_cmd.append('-I' + os.path.dirname(flags.input))
    147  if flags.show_includes:
    148    clang_cmd.append('/showIncludes')
    149  if flags.werror:
    150    clang_cmd.append('/WX')
    151  clang_cmd += flags.imsvcs + flags.winsysroot + flags.includes + flags.defines
    152  p = subprocess.Popen(clang_cmd, stdin=subprocess.PIPE)
    153  p.communicate(input=rc_file_data)
    154  if p.returncode != 0:
    155    sys.exit(p.returncode)
    156  preprocessed_output = open(temp_file, 'rb').read()
    157  os.remove(temp_file)
    158 
    159  # rc.exe has a wacko preprocessor:
    160  # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381033(v=vs.85).aspx
    161  # """RC treats files with the .c and .h extensions in a special manner. It
    162  # assumes that a file with one of these extensions does not contain
    163  # resources. If a file has the .c or .h file name extension, RC ignores all
    164  # lines in the file except the preprocessor directives."""
    165  # Thankfully, the Microsoft headers are mostly good about putting everything
    166  # in the system headers behind `if !defined(RC_INVOKED)`, so regular
    167  # preprocessing with RC_INVOKED defined works.
    168  return preprocessed_output
    169 
    170 
    171 def RunRc(preprocessed_output, is_utf8, flags):
    172  if sys.platform.startswith('linux'):
    173    rc = os.path.join(THIS_DIR, 'linux64', 'rc')
    174  elif sys.platform == 'darwin':
    175    rc = os.path.join(THIS_DIR, 'mac', 'rc')
    176  elif sys.platform == 'win32':
    177    rc = os.path.join(THIS_DIR, 'win', 'rc.exe')
    178  else:
    179    print('rc.py: error: unsupported platform', sys.platform, file=sys.stderr)
    180    sys.exit(1)
    181  rc_cmd = [rc]
    182  # Make sure rc-relative resources can be found:
    183  if os.path.dirname(flags.input):
    184    rc_cmd.append('/cd' + os.path.dirname(flags.input))
    185  rc_cmd.append('/fo' + flags.output)
    186  if is_utf8:
    187    rc_cmd.append('/utf-8')
    188  # TODO(thakis): cl currently always prints full paths for /showIncludes,
    189  # but clang-cl /P doesn't.  Which one is right?
    190  if flags.show_includes:
    191    rc_cmd.append('/showIncludes')
    192  # Microsoft rc.exe searches for referenced files relative to -I flags in
    193  # addition to the pwd, so -I flags need to be passed both to both
    194  # the preprocessor and rc.
    195  rc_cmd += flags.includes
    196  p = subprocess.Popen(rc_cmd, stdin=subprocess.PIPE)
    197  p.communicate(input=preprocessed_output)
    198 
    199  if flags.show_includes and p.returncode == 0:
    200    TOOL_DIR = os.path.dirname(os.path.relpath(THIS_DIR)).replace("\\", "/")
    201    # Since tool("rc") can't have deps, add deps on this script and on rc.py
    202    # and its deps here, so that rc edges become dirty if rc.py changes.
    203    print('Note: including file: {}/tool_wrapper.py'.format(TOOL_DIR))
    204    print('Note: including file: {}/rc/rc.py'.format(TOOL_DIR))
    205    print(
    206        'Note: including file: {}/rc/linux64/rc.sha1'.format(TOOL_DIR))
    207    print('Note: including file: {}/rc/mac/rc.sha1'.format(TOOL_DIR))
    208    print(
    209        'Note: including file: {}/rc/win/rc.exe.sha1'.format(TOOL_DIR))
    210 
    211  return p.returncode
    212 
    213 
    214 def CompareToMsRcOutput(preprocessed_output, is_utf8, flags):
    215  msrc_in = flags.output + '.preprocessed.rc'
    216 
    217  # Strip preprocessor line markers.
    218  preprocessed_output = re.sub(br'^#.*$', b'', preprocessed_output, flags=re.M)
    219  if is_utf8:
    220    preprocessed_output = preprocessed_output.decode('utf-8').encode('utf-16le')
    221  with open(msrc_in, 'wb') as f:
    222    f.write(preprocessed_output)
    223 
    224  msrc_out = flags.output + '_ms_rc'
    225  msrc_cmd = ['rc', '/nologo', '/x', '/fo' + msrc_out]
    226 
    227  # Make sure rc-relative resources can be found. rc.exe looks for external
    228  # resource files next to the file, but the preprocessed file isn't where the
    229  # input was.
    230  # Note that rc searches external resource files in the order of
    231  # 1. next to the input file
    232  # 2. relative to cwd
    233  # 3. next to -I directories
    234  # Changing the cwd means we'd have to rewrite all -I flags, so just add
    235  # the input file dir as -I flag. That technically gets the order of 1 and 2
    236  # wrong, but in Chromium's build the cwd is the gn out dir, and generated
    237  # files there are in obj/ and gen/, so this difference doesn't matter in
    238  # practice.
    239  if os.path.dirname(flags.input):
    240    msrc_cmd += [ '-I' + os.path.dirname(flags.input) ]
    241 
    242  # Microsoft rc.exe searches for referenced files relative to -I flags in
    243  # addition to the pwd, so -I flags need to be passed both to both
    244  # the preprocessor and rc.
    245  msrc_cmd += flags.includes
    246 
    247  # Input must come last.
    248  msrc_cmd += [ msrc_in ]
    249 
    250  rc_exe_exit_code = subprocess.call(msrc_cmd)
    251  # Assert Microsoft rc.exe and rc.py produced identical .res files.
    252  if rc_exe_exit_code == 0:
    253    import filecmp
    254    assert filecmp.cmp(msrc_out, flags.output)
    255  return rc_exe_exit_code
    256 
    257 
    258 def main():
    259  # This driver has to do these things:
    260  # 1. Parse flags.
    261  # 2. Convert the input from UTF-16LE to UTF-8 if needed.
    262  # 3. Pass the input through a preprocessor (and clean up the preprocessor's
    263  #    output in minor ways).
    264  # 4. Call rc for the heavy lifting.
    265  flags = ParseFlags()
    266  rc_file_data, is_utf8 = ReadInput(flags.input)
    267  preprocessed_output = Preprocess(rc_file_data, flags)
    268  rc_exe_exit_code = RunRc(preprocessed_output, is_utf8, flags)
    269 
    270  # 5. On Windows, we also call Microsoft's rc.exe and check that we produced
    271  #   the same output.
    272  # Since Microsoft's rc has a preprocessor that only accepts 32 characters
    273  # for macro names, feed the clang-preprocessed source into it instead
    274  # of using ms rc's preprocessor.
    275  if sys.platform == 'win32' and rc_exe_exit_code == 0:
    276    rc_exe_exit_code = CompareToMsRcOutput(preprocessed_output, is_utf8, flags)
    277 
    278  return rc_exe_exit_code
    279 
    280 
    281 if __name__ == '__main__':
    282  sys.exit(main())