• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import glob
7import json
8import os
9import pipes
10import shutil
11import subprocess
12import sys
13
14
15script_dir = os.path.dirname(os.path.realpath(__file__))
16chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
17SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
19json_data_file = os.path.join(script_dir, 'win_toolchain.json')
20
21
22import gyp
23
24
25# Use MSVS2015 as the default toolchain.
26CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2015'
27
28
29def SetEnvironmentAndGetRuntimeDllDirs():
30  """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
31  returns the location of the VS runtime DLLs so they can be copied into
32  the output directory after gyp generation.
33  """
34  vs_runtime_dll_dirs = None
35  depot_tools_win_toolchain = \
36      bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
37  # When running on a non-Windows host, only do this if the SDK has explicitly
38  # been downloaded before (in which case json_data_file will exist).
39  if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
40      and depot_tools_win_toolchain):
41    if ShouldUpdateToolchain():
42      Update()
43    with open(json_data_file, 'r') as tempf:
44      toolchain_data = json.load(tempf)
45
46    toolchain = toolchain_data['path']
47    version = toolchain_data['version']
48    win_sdk = toolchain_data.get('win_sdk')
49    if not win_sdk:
50      win_sdk = toolchain_data['win8sdk']
51    wdk = toolchain_data['wdk']
52    # TODO(scottmg): The order unfortunately matters in these. They should be
53    # split into separate keys for x86 and x64. (See CopyVsRuntimeDlls call
54    # below). http://crbug.com/345992
55    vs_runtime_dll_dirs = toolchain_data['runtime_dirs']
56
57    os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
58    os.environ['GYP_MSVS_VERSION'] = version
59    # We need to make sure windows_sdk_path is set to the automated
60    # toolchain values in GYP_DEFINES, but don't want to override any
61    # otheroptions.express
62    # values there.
63    gyp_defines_dict = gyp.NameValueListToDict(gyp.ShlexEnv('GYP_DEFINES'))
64    gyp_defines_dict['windows_sdk_path'] = win_sdk
65    os.environ['GYP_DEFINES'] = ' '.join('%s=%s' % (k, pipes.quote(str(v)))
66        for k, v in gyp_defines_dict.iteritems())
67    os.environ['WINDOWSSDKDIR'] = win_sdk
68    os.environ['WDK_DIR'] = wdk
69    # Include the VS runtime in the PATH in case it's not machine-installed.
70    runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs)
71    os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH']
72  elif sys.platform == 'win32' and not depot_tools_win_toolchain:
73    if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
74      os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
75    if not 'GYP_MSVS_VERSION' in os.environ:
76      os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
77
78  return vs_runtime_dll_dirs
79
80
81def _RegistryGetValueUsingWinReg(key, value):
82  """Use the _winreg module to obtain the value of a registry key.
83
84  Args:
85    key: The registry key.
86    value: The particular registry value to read.
87  Return:
88    contents of the registry key's value, or None on failure.  Throws
89    ImportError if _winreg is unavailable.
90  """
91  import _winreg
92  try:
93    root, subkey = key.split('\\', 1)
94    assert root == 'HKLM'  # Only need HKLM for now.
95    with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
96      return _winreg.QueryValueEx(hkey, value)[0]
97  except WindowsError:
98    return None
99
100
101def _RegistryGetValue(key, value):
102  try:
103    return _RegistryGetValueUsingWinReg(key, value)
104  except ImportError:
105    raise Exception('The python library _winreg not found.')
106
107
108def GetVisualStudioVersion():
109  """Return GYP_MSVS_VERSION of Visual Studio.
110  """
111  return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION)
112
113
114def DetectVisualStudioPath():
115  """Return path to the GYP_MSVS_VERSION of Visual Studio.
116  """
117
118  # Note that this code is used from
119  # build/toolchain/win/setup_toolchain.py as well.
120  version_as_year = GetVisualStudioVersion()
121  year_to_version = {
122      '2013': '12.0',
123      '2015': '14.0',
124  }
125  if version_as_year not in year_to_version:
126    raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
127                     ' not supported. Supported versions are: %s') % (
128                       version_as_year, ', '.join(year_to_version.keys())))
129  version = year_to_version[version_as_year]
130  keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version,
131          r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version]
132  for key in keys:
133    path = _RegistryGetValue(key, 'InstallDir')
134    if not path:
135      continue
136    path = os.path.normpath(os.path.join(path, '..', '..'))
137    return path
138
139  raise Exception(('Visual Studio Version %s (from GYP_MSVS_VERSION)'
140                   ' not found.') % (version_as_year))
141
142
143def _VersionNumber():
144  """Gets the standard version number ('120', '140', etc.) based on
145  GYP_MSVS_VERSION."""
146  vs_version = GetVisualStudioVersion()
147  if vs_version == '2013':
148    return '120'
149  elif vs_version == '2015':
150    return '140'
151  else:
152    raise ValueError('Unexpected GYP_MSVS_VERSION')
153
154
155def _CopyRuntimeImpl(target, source, verbose=True):
156  """Copy |source| to |target| if it doesn't already exist or if it needs to be
157  updated (comparing last modified time as an approximate float match as for
158  some reason the values tend to differ by ~1e-07 despite being copies of the
159  same file... https://crbug.com/603603).
160  """
161  if (os.path.isdir(os.path.dirname(target)) and
162      (not os.path.isfile(target) or
163       abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)):
164    if verbose:
165      print 'Copying %s to %s...' % (source, target)
166    if os.path.exists(target):
167      os.unlink(target)
168    shutil.copy2(source, target)
169
170
171def _CopyRuntime2013(target_dir, source_dir, dll_pattern):
172  """Copy both the msvcr and msvcp runtime DLLs, only if the target doesn't
173  exist, but the target directory does exist."""
174  for file_part in ('p', 'r'):
175    dll = dll_pattern % file_part
176    target = os.path.join(target_dir, dll)
177    source = os.path.join(source_dir, dll)
178    _CopyRuntimeImpl(target, source)
179
180
181def _CopyRuntime2015(target_dir, source_dir, dll_pattern, suffix):
182  """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
183  exist, but the target directory does exist."""
184  for file_part in ('msvcp', 'vccorlib', 'vcruntime'):
185    dll = dll_pattern % file_part
186    target = os.path.join(target_dir, dll)
187    source = os.path.join(source_dir, dll)
188    _CopyRuntimeImpl(target, source)
189  ucrt_src_dir = os.path.join(source_dir, 'api-ms-win-*.dll')
190  for ucrt_src_file in glob.glob(ucrt_src_dir):
191    file_part = os.path.basename(ucrt_src_file)
192    ucrt_dst_file = os.path.join(target_dir, file_part)
193    _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
194  _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
195                    os.path.join(source_dir, 'ucrtbase' + suffix))
196
197
198def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
199  """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
200  directory does exist. Handles VS 2013 and VS 2015."""
201  suffix = "d.dll" if debug else ".dll"
202  if GetVisualStudioVersion() == '2015':
203    _CopyRuntime2015(target_dir, source_dir, '%s140' + suffix, suffix)
204  else:
205    _CopyRuntime2013(target_dir, source_dir, 'msvc%s120' + suffix)
206
207  # Copy the PGO runtime library to the release directories.
208  if not debug and os.environ.get('GYP_MSVS_OVERRIDE_PATH'):
209    pgo_x86_runtime_dir = os.path.join(os.environ.get('GYP_MSVS_OVERRIDE_PATH'),
210                                        'VC', 'bin')
211    pgo_x64_runtime_dir = os.path.join(pgo_x86_runtime_dir, 'amd64')
212    pgo_runtime_dll = 'pgort' + _VersionNumber() + '.dll'
213    if target_cpu == "x86":
214      source_x86 = os.path.join(pgo_x86_runtime_dir, pgo_runtime_dll)
215      if os.path.exists(source_x86):
216        _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll), source_x86)
217    elif target_cpu == "x64":
218      source_x64 = os.path.join(pgo_x64_runtime_dir, pgo_runtime_dll)
219      if os.path.exists(source_x64):
220        _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll),
221                          source_x64)
222    else:
223      raise NotImplementedError("Unexpected target_cpu value:" + target_cpu)
224
225
226def CopyVsRuntimeDlls(output_dir, runtime_dirs):
227  """Copies the VS runtime DLLs from the given |runtime_dirs| to the output
228  directory so that even if not system-installed, built binaries are likely to
229  be able to run.
230
231  This needs to be run after gyp has been run so that the expected target
232  output directories are already created.
233
234  This is used for the GYP build and gclient runhooks.
235  """
236  x86, x64 = runtime_dirs
237  out_debug = os.path.join(output_dir, 'Debug')
238  out_debug_nacl64 = os.path.join(output_dir, 'Debug', 'x64')
239  out_release = os.path.join(output_dir, 'Release')
240  out_release_nacl64 = os.path.join(output_dir, 'Release', 'x64')
241  out_debug_x64 = os.path.join(output_dir, 'Debug_x64')
242  out_release_x64 = os.path.join(output_dir, 'Release_x64')
243
244  if os.path.exists(out_debug) and not os.path.exists(out_debug_nacl64):
245    os.makedirs(out_debug_nacl64)
246  if os.path.exists(out_release) and not os.path.exists(out_release_nacl64):
247    os.makedirs(out_release_nacl64)
248  _CopyRuntime(out_debug,          x86, "x86", debug=True)
249  _CopyRuntime(out_release,        x86, "x86", debug=False)
250  _CopyRuntime(out_debug_x64,      x64, "x64", debug=True)
251  _CopyRuntime(out_release_x64,    x64, "x64", debug=False)
252  _CopyRuntime(out_debug_nacl64,   x64, "x64", debug=True)
253  _CopyRuntime(out_release_nacl64, x64, "x64", debug=False)
254
255
256def CopyDlls(target_dir, configuration, target_cpu):
257  """Copy the VS runtime DLLs into the requested directory as needed.
258
259  configuration is one of 'Debug' or 'Release'.
260  target_cpu is one of 'x86' or 'x64'.
261
262  The debug configuration gets both the debug and release DLLs; the
263  release config only the latter.
264
265  This is used for the GN build.
266  """
267  vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
268  if not vs_runtime_dll_dirs:
269    return
270
271  x64_runtime, x86_runtime = vs_runtime_dll_dirs
272  runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime
273  _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
274  if configuration == 'Debug':
275    _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
276
277
278def _GetDesiredVsToolchainHashes():
279  """Load a list of SHA1s corresponding to the toolchains that we want installed
280  to build with."""
281  if GetVisualStudioVersion() == '2015':
282    if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN_PRERELEASE', '0'))):
283      # Update 3 pre-release, May 16th.
284      return ['283cc362f57dbe240e0d21f48ae45f9d834a425a']
285    else:
286      # Update 2.
287      return ['95ddda401ec5678f15eeed01d2bee08fcbc5ee97']
288  else:
289    return ['03a4e939cd325d6bc5216af41b92d02dda1366a6']
290
291
292def ShouldUpdateToolchain():
293  """Check if the toolchain should be upgraded."""
294  if not os.path.exists(json_data_file):
295    return True
296  with open(json_data_file, 'r') as tempf:
297    toolchain_data = json.load(tempf)
298  version = toolchain_data['version']
299  env_version = GetVisualStudioVersion()
300  # If there's a mismatch between the version set in the environment and the one
301  # in the json file then the toolchain should be updated.
302  return version != env_version
303
304
305def Update(force=False):
306  """Requests an update of the toolchain to the specific hashes we have at
307  this revision. The update outputs a .json of the various configuration
308  information required to pass to gyp which we use in |GetToolchainDir()|.
309  """
310  if force != False and force != '--force':
311    print >>sys.stderr, 'Unknown parameter "%s"' % force
312    return 1
313  if force == '--force' or os.path.exists(json_data_file):
314    force = True
315
316  depot_tools_win_toolchain = \
317      bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
318  if ((sys.platform in ('win32', 'cygwin') or force) and
319        depot_tools_win_toolchain):
320    import find_depot_tools
321    depot_tools_path = find_depot_tools.add_depot_tools_to_path()
322    # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit
323    # in the correct directory.
324    os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
325    get_toolchain_args = [
326        sys.executable,
327        os.path.join(depot_tools_path,
328                    'win_toolchain',
329                    'get_toolchain_if_necessary.py'),
330        '--output-json', json_data_file,
331      ] + _GetDesiredVsToolchainHashes()
332    if force:
333      get_toolchain_args.append('--force')
334    subprocess.check_call(get_toolchain_args)
335
336  return 0
337
338
339def NormalizePath(path):
340  while path.endswith("\\"):
341    path = path[:-1]
342  return path
343
344
345def GetToolchainDir():
346  """Gets location information about the current toolchain (must have been
347  previously updated by 'update'). This is used for the GN build."""
348  runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
349
350  # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
351  if not 'WINDOWSSDKDIR' in os.environ:
352    default_sdk_path = 'C:\\Program Files (x86)\\Windows Kits\\10'
353    if os.path.isdir(default_sdk_path):
354      os.environ['WINDOWSSDKDIR'] = default_sdk_path
355
356  print '''vs_path = "%s"
357sdk_path = "%s"
358vs_version = "%s"
359wdk_dir = "%s"
360runtime_dirs = "%s"
361''' % (
362      NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH']),
363      NormalizePath(os.environ['WINDOWSSDKDIR']),
364      GetVisualStudioVersion(),
365      NormalizePath(os.environ.get('WDK_DIR', '')),
366      os.path.pathsep.join(runtime_dll_dirs or ['None']))
367
368
369def main():
370  commands = {
371      'update': Update,
372      'get_toolchain_dir': GetToolchainDir,
373      'copy_dlls': CopyDlls,
374  }
375  if len(sys.argv) < 2 or sys.argv[1] not in commands:
376    print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands)
377    return 1
378  return commands[sys.argv[1]](*sys.argv[2:])
379
380
381if __name__ == '__main__':
382  sys.exit(main())
383