| Index: win_toolchain/toolchain2013.py
 | 
| diff --git a/win_toolchain/toolchain2013.py b/win_toolchain/toolchain2013.py
 | 
| new file mode 100755
 | 
| index 0000000000000000000000000000000000000000..2bce633a256a3bc9168cd8e7595a62c5a386d977
 | 
| --- /dev/null
 | 
| +++ b/win_toolchain/toolchain2013.py
 | 
| @@ -0,0 +1,290 @@
 | 
| +#!/usr/bin/env python
 | 
| +# Copyright 2013 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.
 | 
| +
 | 
| +"""Extracts a Windows VS2013 toolchain from various downloadable pieces."""
 | 
| +
 | 
| +
 | 
| +import ctypes
 | 
| +import optparse
 | 
| +import os
 | 
| +import shutil
 | 
| +import subprocess
 | 
| +import sys
 | 
| +import tempfile
 | 
| +import urllib2
 | 
| +
 | 
| +
 | 
| +BASEDIR = os.path.dirname(os.path.abspath(__file__))
 | 
| +g_temp_dirs = []
 | 
| +
 | 
| +
 | 
| +def GetLongPathName(path):
 | 
| +  """Converts any 8dot3 names in the path to the full name."""
 | 
| +  buf = ctypes.create_unicode_buffer(260)
 | 
| +  size = ctypes.windll.kernel32.GetLongPathNameW(unicode(path), buf, 260)
 | 
| +  if (size > 260):
 | 
| +    sys.exit('Long form of path longer than 260 chars: %s' % path)
 | 
| +  return buf.value
 | 
| +
 | 
| +
 | 
| +def RunOrDie(command):
 | 
| +  subprocess.check_call(command, shell=True)
 | 
| +
 | 
| +
 | 
| +def TempDir():
 | 
| +  """Generates a temporary directory (for downloading or extracting to) and keep
 | 
| +  track of the directory that's created for cleaning up later.
 | 
| +  """
 | 
| +  global g_temp_dirs
 | 
| +  temp = tempfile.mkdtemp()
 | 
| +  g_temp_dirs.append(temp)
 | 
| +  return temp
 | 
| +
 | 
| +
 | 
| +def DeleteAllTempDirs():
 | 
| +  """Removes all temporary directories created by |TempDir()|."""
 | 
| +  global g_temp_dirs
 | 
| +  if g_temp_dirs:
 | 
| +    sys.stdout.write('Cleaning up temporaries...\n')
 | 
| +  for temp in g_temp_dirs:
 | 
| +    # shutil.rmtree errors out on read only attributes.
 | 
| +    RunOrDie('rmdir /s/q "%s"' % temp)
 | 
| +  g_temp_dirs = []
 | 
| +
 | 
| +
 | 
| +def GetIsoUrl(pro):
 | 
| +  """Gets the .iso URL.
 | 
| +
 | 
| +  If |pro| is False, downloads the Express edition.
 | 
| +  """
 | 
| +  prefix = 'http://download.microsoft.com/download/'
 | 
| +  if pro:
 | 
| +    return (prefix +
 | 
| +        'A/F/1/AF128362-A6A8-4DB3-A39A-C348086472CC/VS2013_RTM_PRO_ENU.iso')
 | 
| +  else:
 | 
| +    return (prefix +
 | 
| +        '7/2/E/72E0F986-D247-4289-B9DC-C4FB07374894/VS2013_RTM_DskExp_ENU.iso')
 | 
| +
 | 
| +
 | 
| +def Download(url, local_path):
 | 
| +  """Downloads a large-ish binary file and print some status information while
 | 
| +  doing so.
 | 
| +  """
 | 
| +  sys.stdout.write('Downloading %s...\n' % url)
 | 
| +  req = urllib2.urlopen(url)
 | 
| +  content_length = int(req.headers.get('Content-Length', 0))
 | 
| +  bytes_read = 0L
 | 
| +  terminator = '\r' if sys.stdout.isatty() else '\n'
 | 
| +  with open(local_path, 'wb') as file:
 | 
| +    while True:
 | 
| +      chunk = req.read(1024 * 1024)
 | 
| +      if not chunk:
 | 
| +        break
 | 
| +      bytes_read += len(chunk)
 | 
| +      file.write(chunk)
 | 
| +      sys.stdout.write('... %d/%d%s' % (bytes_read, content_length, terminator))
 | 
| +      sys.stdout.flush()
 | 
| +  sys.stdout.write('\n')
 | 
| +  if content_length and content_length != bytes_read:
 | 
| +    raise SystemExit('Got incorrect number of bytes downloading %s' % url)
 | 
| +
 | 
| +
 | 
| +def ExtractIso(iso_path):
 | 
| +  """Uses 7zip to extract the contents of the given .iso (or self-extracting
 | 
| +  .exe).
 | 
| +  """
 | 
| +  target_path = TempDir()
 | 
| +  sys.stdout.write('Extracting %s...\n' % iso_path)
 | 
| +  sys.stdout.flush()
 | 
| +  # TODO(scottmg): Do this (and exe) manually with python code.
 | 
| +  # Note that at the beginning of main() we set the working directory to 7z's
 | 
| +  # location so that 7z can find its codec dll.
 | 
| +  RunOrDie('7z x "%s" -y "-o%s" >nul' % (iso_path, target_path))
 | 
| +  return target_path
 | 
| +
 | 
| +
 | 
| +def ExtractMsi(msi_path):
 | 
| +  """Uses msiexec to extract the contents of the given .msi file."""
 | 
| +  sys.stdout.write('Extracting %s...\n' % msi_path)
 | 
| +  target_path = TempDir()
 | 
| +  RunOrDie('msiexec /a "%s" /qn TARGETDIR="%s"' % (msi_path, target_path))
 | 
| +  return target_path
 | 
| +
 | 
| +
 | 
| +def DownloadMainIso(url):
 | 
| +  temp_dir = TempDir()
 | 
| +  target_path = os.path.join(temp_dir, os.path.basename(url))
 | 
| +  Download(url, target_path)
 | 
| +  return target_path
 | 
| +
 | 
| +
 | 
| +def GetSourceImage(local_dir, pro):
 | 
| +  url = GetIsoUrl(pro)
 | 
| +  if local_dir:
 | 
| +    return os.path.join(local_dir, os.path.basename(url))
 | 
| +  else:
 | 
| +    return DownloadMainIso(url)
 | 
| +
 | 
| +
 | 
| +def ExtractMsiList(iso_dir, packages):
 | 
| +  """Extracts the contents of a list of .msi files from an already extracted
 | 
| +  .iso file.
 | 
| +
 | 
| +  |packages| is a list of pairs (msi, required). If required is not True, the
 | 
| +  msi is optional (this is set for packages that are in Pro but not Express).
 | 
| +  """
 | 
| +  results = []
 | 
| +  for (package, required) in packages:
 | 
| +    path_to_package = os.path.join(iso_dir, 'packages', package)
 | 
| +    if not os.path.exists(path_to_package) and not required:
 | 
| +      continue
 | 
| +    results.append(ExtractMsi(path_to_package))
 | 
| +  return results
 | 
| +
 | 
| +
 | 
| +def ExtractComponents(image):
 | 
| +  packages = [
 | 
| +      (r'vcRuntimeAdditional_amd64\vc_runtimeAdditional_x64.msi', True),
 | 
| +      (r'vcRuntimeAdditional_x86\vc_runtimeAdditional_x86.msi', True),
 | 
| +      (r'vcRuntimeDebug_amd64\vc_runtimeDebug_x64.msi', True),
 | 
| +      (r'vcRuntimeDebug_x86\vc_runtimeDebug_x86.msi', True),
 | 
| +      (r'vcRuntimeMinimum_amd64\vc_runtimeMinimum_x64.msi', True),
 | 
| +      (r'vcRuntimeMinimum_x86\vc_runtimeMinimum_x86.msi', True),
 | 
| +      (r'vc_compilerCore86\vc_compilerCore86.msi', True),
 | 
| +      (r'vc_compilerCore86res\vc_compilerCore86res.msi', True),
 | 
| +      (r'vc_compilerx64nat\vc_compilerx64nat.msi', False),
 | 
| +      (r'vc_compilerx64natres\vc_compilerx64natres.msi', False),
 | 
| +      (r'vc_compilerx64x86\vc_compilerx64x86.msi', False),
 | 
| +      (r'vc_compilerx64x86res\vc_compilerx64x86res.msi', False),
 | 
| +      (r'vc_librarycore86\vc_librarycore86.msi', True),
 | 
| +      (r'vc_libraryDesktop\x64\vc_LibraryDesktopX64.msi', True),
 | 
| +      (r'vc_libraryDesktop\x86\vc_LibraryDesktopX86.msi', True),
 | 
| +      (r'vc_libraryextended\vc_libraryextended.msi', False),
 | 
| +      (r'Windows_SDK\Windows Software Development Kit-x86_en-us.msi', True),
 | 
| +      ('Windows_SDK\\'
 | 
| +       r'Windows Software Development Kit for Metro style Apps-x86_en-us.msi',
 | 
| +          True),
 | 
| +    ]
 | 
| +  extracted_iso = ExtractIso(image)
 | 
| +  return ExtractMsiList(extracted_iso, packages)
 | 
| +
 | 
| +
 | 
| +def CopyToFinalLocation(extracted_dirs, target_dir):
 | 
| +  sys.stdout.write('Copying to final location...\n')
 | 
| +  mappings = {
 | 
| +      'Program Files\\Microsoft Visual Studio 12.0\\': '.\\',
 | 
| +      'System64\\': 'sys64\\',
 | 
| +      'System\\': 'sys32\\',
 | 
| +      'Windows Kits\\8.0\\': 'win8sdk\\',
 | 
| +  }
 | 
| +  matches = []
 | 
| +  for extracted_dir in extracted_dirs:
 | 
| +    for root, dirnames, filenames in os.walk(extracted_dir):
 | 
| +      for filename in filenames:
 | 
| +        matches.append((extracted_dir, os.path.join(root, filename)))
 | 
| +
 | 
| +  copies = []
 | 
| +  for prefix, full_path in matches:
 | 
| +    # +1 for trailing \.
 | 
| +    partial_path = full_path[len(prefix) + 1:]
 | 
| +    for map_from, map_to in mappings.iteritems():
 | 
| +      if partial_path.startswith(map_from):
 | 
| +        target_path = os.path.join(map_to, partial_path[len(map_from):])
 | 
| +        copies.append((full_path, os.path.join(target_dir, target_path)))
 | 
| +
 | 
| +  for full_source, full_target in copies:
 | 
| +    target_dir = os.path.dirname(full_target)
 | 
| +    if not os.path.isdir(target_dir):
 | 
| +      os.makedirs(target_dir)
 | 
| +    shutil.copy2(full_source, full_target)
 | 
| +
 | 
| +
 | 
| +def GenerateSetEnvCmd(target_dir, pro):
 | 
| +  """Generate a batch file that gyp expects to exist to set up the compiler
 | 
| +  environment.
 | 
| +
 | 
| +  This is normally generated by a full install of the SDK, but we
 | 
| +  do it here manually since we do not do a full install."""
 | 
| +  with open(os.path.join(
 | 
| +        target_dir, r'win8sdk\bin\SetEnv.cmd'), 'w') as f:
 | 
| +    f.write('@echo off\n'
 | 
| +            ':: Generated by win_toolchain\\toolchain2013.py.\n'
 | 
| +            # Common to x86 and x64
 | 
| +            'set PATH=%~dp0..\\..\\Common7\\IDE;%PATH%\n'
 | 
| +            'set INCLUDE=%~dp0..\\..\\win8sdk\\Include\\um;'
 | 
| +               '%~dp0..\\..\\win8sdk\\Include\\shared;'
 | 
| +               '%~dp0..\\..\\VC\\include;'
 | 
| +               '%~dp0..\\..\\VC\\atlmfc\\include\n'
 | 
| +            'if "%1"=="/x64" goto x64\n')
 | 
| +
 | 
| +    # x86. If we're Pro, then use the amd64_x86 cross (we don't support x86
 | 
| +    # host at all).
 | 
| +    if pro:
 | 
| +      f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;'
 | 
| +                '%~dp0..\\..\\VC\\bin\\amd64_x86;'
 | 
| +                '%~dp0..\\..\\VC\\bin\\amd64;'  # Needed for mspdb120.dll.
 | 
| +                '%PATH%\n')
 | 
| +    else:
 | 
| +      f.write('set PATH=%~dp0..\\..\\win8sdk\\bin\\x86;'
 | 
| +                '%~dp0..\\..\\VC\\bin;%PATH%\n')
 | 
| +    f.write('set LIB=%~dp0..\\..\\VC\\lib;'
 | 
| +               '%~dp0..\\..\\win8sdk\\Lib\\win8\\um\\x86;'
 | 
| +               '%~dp0..\\..\\VC\\atlmfc\\lib\n'
 | 
| +            'goto :EOF\n')
 | 
| +
 | 
| +    # Express does not include a native 64 bit compiler, so we have to use
 | 
| +    # the x86->x64 cross.
 | 
| +    if not pro:
 | 
| +      # x86->x64 cross.
 | 
| +      f.write(':x64\n'
 | 
| +              'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;'
 | 
| +                 '%~dp0..\\..\\VC\\bin\\x86_amd64;'
 | 
| +                 '%PATH%\n')
 | 
| +    else:
 | 
| +      # x64 native.
 | 
| +      f.write(':x64\n'
 | 
| +              'set PATH=%~dp0..\\..\\win8sdk\\bin\\x64;'
 | 
| +                 '%~dp0..\\..\\VC\\bin\\amd64;'
 | 
| +                 '%PATH%\n')
 | 
| +    f.write('set LIB=%~dp0..\\..\\VC\\lib\\amd64;'
 | 
| +               '%~dp0..\\..\\win8sdk\\Lib\\win8\\um\\x64;'
 | 
| +               '%~dp0..\\..\\VC\\atlmfc\\lib\\amd64\n')
 | 
| +
 | 
| +
 | 
| +def main():
 | 
| +  parser = optparse.OptionParser(description=sys.modules[__name__].__doc__)
 | 
| +  parser.add_option('--targetdir', metavar='DIR',
 | 
| +                    help='put toolchain into DIR',
 | 
| +                    default=os.path.join(BASEDIR, 'win_toolchain_2013'))
 | 
| +  parser.add_option('--noclean', action='store_false', dest='clean',
 | 
| +                    help='do not remove temp files',
 | 
| +                    default=True)
 | 
| +  parser.add_option('--local', metavar='DIR',
 | 
| +                    help='use downloaded files from DIR')
 | 
| +  parser.add_option('--express',
 | 
| +                    help='use VS Express instead of Pro', action='store_true')
 | 
| +  options, args = parser.parse_args()
 | 
| +  try:
 | 
| +    target_dir = os.path.abspath(options.targetdir)
 | 
| +    if os.path.exists(target_dir):
 | 
| +      parser.error('%s already exists. Please [re]move it or use '
 | 
| +                   '--targetdir to select a different target.\n' %
 | 
| +                   target_dir)
 | 
| +    # Set the working directory to 7z subdirectory. 7-zip doesn't find its
 | 
| +    # codec dll very well, so this is the simplest way to make sure it runs
 | 
| +    # correctly, as we don't otherwise care about working directory.
 | 
| +    os.chdir(os.path.join(BASEDIR, '7z'))
 | 
| +    image = GetSourceImage(options.local, not options.express)
 | 
| +    extracted = ExtractComponents(image)
 | 
| +    CopyToFinalLocation(extracted, target_dir)
 | 
| +
 | 
| +    GenerateSetEnvCmd(target_dir, not options.express)
 | 
| +  finally:
 | 
| +    if options.clean:
 | 
| +      DeleteAllTempDirs()
 | 
| +
 | 
| +
 | 
| +if __name__ == '__main__':
 | 
| +  sys.exit(main())
 | 
| 
 |