tor-browser

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

tool_wrapper.py (7183B)


      1 # Copyright 2012 The Chromium Authors
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Utility functions for Windows builds.
      6 
      7 This file is copied to the build directory as part of toolchain setup and
      8 is used to set up calls to tools used by the build that need wrappers.
      9 """
     10 
     11 
     12 import os
     13 import re
     14 import shutil
     15 import subprocess
     16 import stat
     17 import sys
     18 
     19 
     20 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
     21 
     22 # A regex matching an argument corresponding to the output filename passed to
     23 # link.exe.
     24 _LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
     25 
     26 def main(args):
     27  exit_code = WinTool().Dispatch(args)
     28  if exit_code is not None:
     29    sys.exit(exit_code)
     30 
     31 
     32 class WinTool(object):
     33  """This class performs all the Windows tooling steps. The methods can either
     34  be executed directly, or dispatched from an argument list."""
     35 
     36  def _UseSeparateMspdbsrv(self, env, args):
     37    """Allows to use a unique instance of mspdbsrv.exe per linker instead of a
     38    shared one."""
     39    if len(args) < 1:
     40      raise Exception("Not enough arguments")
     41 
     42    if args[0] != 'link.exe':
     43      return
     44 
     45    # Use the output filename passed to the linker to generate an endpoint name
     46    # for mspdbsrv.exe.
     47    endpoint_name = None
     48    for arg in args:
     49      m = _LINK_EXE_OUT_ARG.match(arg)
     50      if m:
     51        endpoint_name = re.sub(r'\W+', '',
     52            '%s_%d' % (m.group('out'), os.getpid()))
     53        break
     54 
     55    if endpoint_name is None:
     56      return
     57 
     58    # Adds the appropriate environment variable. This will be read by link.exe
     59    # to know which instance of mspdbsrv.exe it should connect to (if it's
     60    # not set then the default endpoint is used).
     61    env['_MSPDBSRV_ENDPOINT_'] = endpoint_name
     62 
     63  def Dispatch(self, args):
     64    """Dispatches a string command to a method."""
     65    if len(args) < 1:
     66      raise Exception("Not enough arguments")
     67 
     68    method = "Exec%s" % self._CommandifyName(args[0])
     69    return getattr(self, method)(*args[1:])
     70 
     71  def _CommandifyName(self, name_string):
     72    """Transforms a tool name like recursive-mirror to RecursiveMirror."""
     73    return name_string.title().replace('-', '')
     74 
     75  def _GetEnv(self, arch):
     76    """Gets the saved environment from a file for a given architecture."""
     77    # The environment is saved as an "environment block" (see CreateProcess
     78    # and msvs_emulation for details). We convert to a dict here.
     79    # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
     80    pairs = open(arch).read()[:-2].split('\0')
     81    kvs = [item.split('=', 1) for item in pairs]
     82    return dict(kvs)
     83 
     84  def ExecDeleteFile(self, path):
     85    """Simple file delete command."""
     86    if os.path.exists(path):
     87      os.unlink(path)
     88 
     89  def ExecRecursiveMirror(self, source, dest):
     90    """Emulation of rm -rf out && cp -af in out."""
     91    if os.path.exists(dest):
     92      if os.path.isdir(dest):
     93        def _on_error(fn, path, dummy_excinfo):
     94          # The operation failed, possibly because the file is set to
     95          # read-only. If that's why, make it writable and try the op again.
     96          if not os.access(path, os.W_OK):
     97            os.chmod(path, stat.S_IWRITE)
     98          fn(path)
     99        shutil.rmtree(dest, onerror=_on_error)
    100      else:
    101        if not os.access(dest, os.W_OK):
    102          # Attempt to make the file writable before deleting it.
    103          os.chmod(dest, stat.S_IWRITE)
    104        os.unlink(dest)
    105 
    106    if os.path.isdir(source):
    107      shutil.copytree(source, dest)
    108    else:
    109      shutil.copy2(source, dest)
    110      # Try to diagnose crbug.com/741603
    111      if not os.path.exists(dest):
    112        raise Exception("Copying of %s to %s failed" % (source, dest))
    113 
    114  def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
    115    """Filter diagnostic output from link that looks like:
    116    '   Creating library ui.dll.lib and object ui.dll.exp'
    117    This happens when there are exports from the dll or exe.
    118    """
    119    env = self._GetEnv(arch)
    120    if use_separate_mspdbsrv == 'True':
    121      self._UseSeparateMspdbsrv(env, args)
    122    if sys.platform == 'win32':
    123      args = list(args)  # *args is a tuple by default, which is read-only.
    124      args[0] = args[0].replace('/', '\\')
    125    # https://docs.python.org/2/library/subprocess.html:
    126    # "On Unix with shell=True [...] if args is a sequence, the first item
    127    # specifies the command string, and any additional items will be treated as
    128    # additional arguments to the shell itself.  That is to say, Popen does the
    129    # equivalent of:
    130    #   Popen(['/bin/sh', '-c', args[0], args[1], ...])"
    131    # For that reason, since going through the shell doesn't seem necessary on
    132    # non-Windows don't do that there.
    133    pe_name = None
    134    for arg in args:
    135      m = _LINK_EXE_OUT_ARG.match(arg)
    136      if m:
    137        pe_name = m.group('out')
    138    link = subprocess.Popen(args, shell=sys.platform == 'win32', env=env,
    139                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    140    # Read output one line at a time as it shows up to avoid OOM failures when
    141    # GBs of output is produced.
    142    for line in link.stdout:
    143      line = line.decode('utf8')
    144      if (not line.startswith('   Creating library ')
    145          and not line.startswith('Generating code')
    146          and not line.startswith('Finished generating code')):
    147        print(line.rstrip())
    148    return link.wait()
    149 
    150  def ExecAsmWrapper(self, arch, *args):
    151    """Filter logo banner from invocations of asm.exe."""
    152    env = self._GetEnv(arch)
    153    if sys.platform == 'win32':
    154      # Windows ARM64 uses clang-cl as assembler which has '/' as path
    155      # separator, convert it to '\\' when running on Windows.
    156      args = list(args) # *args is a tuple by default, which is read-only
    157      args[0] = args[0].replace('/', '\\')
    158    # See comment in ExecLinkWrapper() for why shell=False on non-win.
    159    popen = subprocess.Popen(args, shell=sys.platform == 'win32', env=env,
    160                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    161    out, _ = popen.communicate()
    162    for line in out.decode('utf8').splitlines():
    163      if not line.startswith(' Assembling: '):
    164        print(line)
    165    return popen.returncode
    166 
    167  def ExecRcWrapper(self, arch, *args):
    168    """Converts .rc files to .res files."""
    169    env = self._GetEnv(arch)
    170    args = list(args)
    171    rcpy_args = args[:]
    172    rcpy_args[0:1] = [sys.executable, os.path.join(BASE_DIR, 'rc', 'rc.py')]
    173    rcpy_args.append('/showIncludes')
    174    return subprocess.call(rcpy_args, env=env)
    175 
    176  def ExecActionWrapper(self, arch, rspfile, *dirname):
    177    """Runs an action command line from a response file using the environment
    178    for |arch|. If |dirname| is supplied, use that as the working directory."""
    179    env = self._GetEnv(arch)
    180    # TODO(scottmg): This is a temporary hack to get some specific variables
    181    # through to actions that are set after GN-time. http://crbug.com/333738.
    182    for k, v in os.environ.items():
    183      if k not in env:
    184        env[k] = v
    185    args = open(rspfile).read()
    186    dirname = dirname[0] if dirname else None
    187    return subprocess.call(args, shell=True, env=env, cwd=dirname)
    188 
    189 
    190 if __name__ == '__main__':
    191  sys.exit(main(sys.argv[1:]))