1#!/usr/bin/env python
2# Copyright 2015 the V8 project authors. All rights reserved.
3# Copyright 2014 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import json
8import os
9import pipes
10import shutil
11import subprocess
12import sys
13import vs_toolchain
14
15
16script_dir = os.path.dirname(os.path.realpath(__file__))
17chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
18SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19sys.path.insert(1, os.path.join(chrome_src, 'tools'))
20sys.path.insert(0, os.path.join(chrome_src, 'build', 'gyp', 'pylib'))
21json_data_file = os.path.join(script_dir, 'win_toolchain.json')
22
23
24import gyp
25
26
27def SetEnvironmentAndGetRuntimeDllDirs():
28  """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
29  returns the location of the VS runtime DLLs so they can be copied into
30  the output directory after gyp generation.
31  """
32  vs2013_runtime_dll_dirs = None
33  depot_tools_win_toolchain = \
34      bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
35  # When running on a non-Windows host, only do this if the SDK has explicitly
36  # been downloaded before (in which case json_data_file will exist).
37  if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
38      and depot_tools_win_toolchain):
39    if not os.path.exists(json_data_file):
40      Update()
41    with open(json_data_file, 'r') as tempf:
42      toolchain_data = json.load(tempf)
43
44    toolchain = toolchain_data['path']
45    version = toolchain_data['version']
46    win_sdk = toolchain_data.get('win_sdk')
47    if not win_sdk:
48      win_sdk = toolchain_data['win8sdk']
49    wdk = toolchain_data['wdk']
50    # TODO(scottmg): The order unfortunately matters in these. They should be
51    # split into separate keys for x86 and x64. (See CopyVsRuntimeDlls call
52    # below). http://crbug.com/345992
53    vs2013_runtime_dll_dirs = toolchain_data['runtime_dirs']
54
55    os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
56    os.environ['GYP_MSVS_VERSION'] = version
57    # We need to make sure windows_sdk_path is set to the automated
58    # toolchain values in GYP_DEFINES, but don't want to override any
59    # otheroptions.express
60    # values there.
61    gyp_defines_dict = gyp.NameValueListToDict(gyp.ShlexEnv('GYP_DEFINES'))
62    gyp_defines_dict['windows_sdk_path'] = win_sdk
63    os.environ['GYP_DEFINES'] = ' '.join('%s=%s' % (k, pipes.quote(str(v)))
64        for k, v in gyp_defines_dict.iteritems())
65    os.environ['WINDOWSSDKDIR'] = win_sdk
66    os.environ['WDK_DIR'] = wdk
67    # Include the VS runtime in the PATH in case it's not machine-installed.
68    runtime_path = ';'.join(vs2013_runtime_dll_dirs)
69    os.environ['PATH'] = runtime_path + ';' + os.environ['PATH']
70  return vs2013_runtime_dll_dirs
71
72
73def _VersionNumber():
74  """Gets the standard version number ('120', '140', etc.) based on
75  GYP_MSVS_VERSION."""
76  if os.environ['GYP_MSVS_VERSION'] == '2013':
77    return '120'
78  elif os.environ['GYP_MSVS_VERSION'] == '2015':
79    return '140'
80  else:
81    raise ValueError('Unexpected GYP_MSVS_VERSION')
82
83
84def _CopyRuntimeImpl(target, source):
85  """Copy |source| to |target| if it doesn't already exist or if it
86  needs to be updated.
87  """
88  if (os.path.isdir(os.path.dirname(target)) and
89      (not os.path.isfile(target) or
90      os.stat(target).st_mtime != os.stat(source).st_mtime)):
91    print 'Copying %s to %s...' % (source, target)
92    if os.path.exists(target):
93      os.unlink(target)
94    shutil.copy2(source, target)
95
96
97def _CopyRuntime2013(target_dir, source_dir, dll_pattern):
98  """Copy both the msvcr and msvcp runtime DLLs, only if the target doesn't
99  exist, but the target directory does exist."""
100  for file_part in ('p', 'r'):
101    dll = dll_pattern % file_part
102    target = os.path.join(target_dir, dll)
103    source = os.path.join(source_dir, dll)
104    _CopyRuntimeImpl(target, source)
105
106
107def _CopyRuntime2015(target_dir, source_dir, dll_pattern):
108  """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
109  exist, but the target directory does exist."""
110  for file_part in ('msvcp', 'vccorlib'):
111    dll = dll_pattern % file_part
112    target = os.path.join(target_dir, dll)
113    source = os.path.join(source_dir, dll)
114    _CopyRuntimeImpl(target, source)
115
116
117def CopyVsRuntimeDlls(output_dir, runtime_dirs):
118  """Copies the VS runtime DLLs from the given |runtime_dirs| to the output
119  directory so that even if not system-installed, built binaries are likely to
120  be able to run.
121
122  This needs to be run after gyp has been run so that the expected target
123  output directories are already created.
124  """
125  x86, x64 = runtime_dirs
126  out_debug = os.path.join(output_dir, 'Debug')
127  out_debug_nacl64 = os.path.join(output_dir, 'Debug', 'x64')
128  out_release = os.path.join(output_dir, 'Release')
129  out_release_nacl64 = os.path.join(output_dir, 'Release', 'x64')
130  out_debug_x64 = os.path.join(output_dir, 'Debug_x64')
131  out_release_x64 = os.path.join(output_dir, 'Release_x64')
132
133  if os.path.exists(out_debug) and not os.path.exists(out_debug_nacl64):
134    os.makedirs(out_debug_nacl64)
135  if os.path.exists(out_release) and not os.path.exists(out_release_nacl64):
136    os.makedirs(out_release_nacl64)
137  if os.environ.get('GYP_MSVS_VERSION') == '2015':
138    _CopyRuntime2015(out_debug,          x86, '%s140d.dll')
139    _CopyRuntime2015(out_release,        x86, '%s140.dll')
140    _CopyRuntime2015(out_debug_x64,      x64, '%s140d.dll')
141    _CopyRuntime2015(out_release_x64,    x64, '%s140.dll')
142    _CopyRuntime2015(out_debug_nacl64,   x64, '%s140d.dll')
143    _CopyRuntime2015(out_release_nacl64, x64, '%s140.dll')
144  else:
145    # VS2013 is the default.
146    _CopyRuntime2013(out_debug,          x86, 'msvc%s120d.dll')
147    _CopyRuntime2013(out_release,        x86, 'msvc%s120.dll')
148    _CopyRuntime2013(out_debug_x64,      x64, 'msvc%s120d.dll')
149    _CopyRuntime2013(out_release_x64,    x64, 'msvc%s120.dll')
150    _CopyRuntime2013(out_debug_nacl64,   x64, 'msvc%s120d.dll')
151    _CopyRuntime2013(out_release_nacl64, x64, 'msvc%s120.dll')
152
153  # Copy the PGO runtime library to the release directories.
154  if os.environ.get('GYP_MSVS_OVERRIDE_PATH'):
155    pgo_x86_runtime_dir = os.path.join(os.environ.get('GYP_MSVS_OVERRIDE_PATH'),
156                                       'VC', 'bin')
157    pgo_x64_runtime_dir = os.path.join(pgo_x86_runtime_dir, 'amd64')
158    pgo_runtime_dll = 'pgort' + _VersionNumber() + '.dll'
159    source_x86 = os.path.join(pgo_x86_runtime_dir, pgo_runtime_dll)
160    if os.path.exists(source_x86):
161      _CopyRuntimeImpl(os.path.join(out_release, pgo_runtime_dll), source_x86)
162    source_x64 = os.path.join(pgo_x64_runtime_dir, pgo_runtime_dll)
163    if os.path.exists(source_x64):
164      _CopyRuntimeImpl(os.path.join(out_release_x64, pgo_runtime_dll),
165                       source_x64)
166
167
168def CopyDlls(target_dir, configuration, target_cpu):
169  """Copy the VS runtime DLLs into the requested directory as needed.
170
171  configuration is one of 'Debug' or 'Release'.
172  target_cpu is one of 'x86' or 'x64'.
173
174  The debug configuration gets both the debug and release DLLs; the
175  release config only the latter.
176  """
177  vs2013_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
178  if not vs2013_runtime_dll_dirs:
179    return
180
181  x64_runtime, x86_runtime = vs2013_runtime_dll_dirs
182  runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime
183  _CopyRuntime2013(
184      target_dir, runtime_dir, 'msvc%s' + _VersionNumber() + '.dll')
185  if configuration == 'Debug':
186    _CopyRuntime2013(
187        target_dir, runtime_dir, 'msvc%s' + _VersionNumber() + 'd.dll')
188
189
190def _GetDesiredVsToolchainHashes():
191  """Load a list of SHA1s corresponding to the toolchains that we want installed
192  to build with."""
193  if os.environ.get('GYP_MSVS_VERSION') == '2015':
194    return ['49ae4b60d898182fc3f521c2fcda82c453915011']
195  else:
196    # Default to VS2013.
197    return ['ee7d718ec60c2dc5d255bbe325909c2021a7efef']
198
199
200def Update(force=False):
201  """Requests an update of the toolchain to the specific hashes we have at
202  this revision. The update outputs a .json of the various configuration
203  information required to pass to gyp which we use in |GetToolchainDir()|.
204  """
205  if force != False and force != '--force':
206    print >>sys.stderr, 'Unknown parameter "%s"' % force
207    return 1
208  if force == '--force' or os.path.exists(json_data_file):
209    force = True
210
211  depot_tools_win_toolchain = \
212      bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
213  if ((sys.platform in ('win32', 'cygwin') or force) and
214        depot_tools_win_toolchain):
215    import find_depot_tools
216    depot_tools_path = find_depot_tools.add_depot_tools_to_path()
217    get_toolchain_args = [
218        sys.executable,
219        os.path.join(depot_tools_path,
220                    'win_toolchain',
221                    'get_toolchain_if_necessary.py'),
222        '--output-json', json_data_file,
223      ] + _GetDesiredVsToolchainHashes()
224    if force:
225      get_toolchain_args.append('--force')
226    subprocess.check_call(get_toolchain_args)
227
228  return 0
229
230
231def GetToolchainDir():
232  """Gets location information about the current toolchain (must have been
233  previously updated by 'update'). This is used for the GN build."""
234  runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
235
236  # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
237  if not 'WINDOWSSDKDIR' in os.environ:
238    default_sdk_path = 'C:\\Program Files (x86)\\Windows Kits\\8.1'
239    if os.path.isdir(default_sdk_path):
240      os.environ['WINDOWSSDKDIR'] = default_sdk_path
241
242  print '''vs_path = "%s"
243sdk_path = "%s"
244vs_version = "%s"
245wdk_dir = "%s"
246runtime_dirs = "%s"
247''' % (
248      os.environ['GYP_MSVS_OVERRIDE_PATH'],
249      os.environ['WINDOWSSDKDIR'],
250      os.environ['GYP_MSVS_VERSION'],
251      os.environ.get('WDK_DIR', ''),
252      ';'.join(runtime_dll_dirs or ['None']))
253
254
255def main():
256  commands = {
257      'update': Update,
258      'get_toolchain_dir': GetToolchainDir,
259      'copy_dlls': CopyDlls,
260  }
261  if len(sys.argv) < 2 or sys.argv[1] not in commands:
262    print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands)
263    return 1
264  return commands[sys.argv[1]](*sys.argv[2:])
265
266
267if __name__ == '__main__':
268  sys.exit(main())
269