• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4#
5# Copies the given "win tool" (which the toolchain uses to wrap compiler
6# invocations) and the environment blocks for the 32-bit and 64-bit builds on
7# Windows to the build directory.
8#
9# The arguments are the visual studio install location and the location of the
10# win tool. The script assumes that the root build directory is the current dir
11# and the files will be written to the current directory.
12
13import errno
14import json
15import os
16import re
17import subprocess
18import sys
19
20SCRIPT_DIR = os.path.dirname(__file__)
21
22def _ExtractImportantEnvironment(output_of_set):
23  """Extracts environment variables required for the toolchain to run from
24  a textual dump output by the cmd.exe 'set' command."""
25  envvars_to_save = (
26      'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
27      'include',
28      'lib',
29      'libpath',
30      'path',
31      'pathext',
32      'systemroot',
33      'temp',
34      'tmp',
35      )
36  env = {}
37  # This occasionally happens and leads to misleading SYSTEMROOT error messages
38  # if not caught here.
39  if output_of_set.count('=') == 0:
40    raise Exception('Invalid output_of_set. Value is:\n%s' % output_of_set)
41  for line in output_of_set.splitlines():
42    for envvar in envvars_to_save:
43      if re.match(envvar + '=', line.lower()):
44        var, setting = line.split('=', 1)
45        if envvar == 'path':
46          # Our own rules (for running gyp-win-tool) and other actions in
47          # Chromium rely on python being in the path. Add the path to this
48          # python here so that if it's not in the path when ninja is run
49          # later, python will still be found.
50          setting = os.path.dirname(sys.executable) + os.pathsep + setting
51        env[var.upper()] = setting
52        break
53  if sys.platform in ('win32', 'cygwin'):
54    for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
55      if required not in env:
56        raise Exception('Environment variable "%s" '
57                        'required to be set to valid path' % required)
58  return env
59
60
61def _DetectVisualStudioPath():
62  """Return path to the GYP_MSVS_VERSION of Visual Studio.
63  """
64
65  # Use the code in build/vs_toolchain.py to avoid duplicating code.
66  chromium_dir = os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..', '..'))
67  sys.path.append(os.path.join(chromium_dir, 'build'))
68  import vs_toolchain
69  return vs_toolchain.DetectVisualStudioPath()
70
71
72def _LoadEnvFromBat(args):
73  """Given a bat command, runs it and returns env vars set by it."""
74  args = args[:]
75  args.extend(('&&', 'set'))
76  popen = subprocess.Popen(
77      args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
78  variables, _ = popen.communicate()
79  if popen.returncode != 0:
80    raise Exception('"%s" failed with error %d' % (args, popen.returncode))
81  return variables
82
83
84def _LoadToolchainEnv(cpu, sdk_dir):
85  """Returns a dictionary with environment variables that must be set while
86  running binaries from the toolchain (e.g. INCLUDE and PATH for cl.exe)."""
87  # Check if we are running in the SDK command line environment and use
88  # the setup script from the SDK if so. |cpu| should be either
89  # 'x86' or 'x64'.
90  assert cpu in ('x86', 'x64')
91  if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and sdk_dir:
92    # Load environment from json file.
93    env = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.%s.json' % cpu))
94    env = json.load(open(env))['env']
95    for k in env:
96      entries = [os.path.join(*([os.path.join(sdk_dir, 'bin')] + e))
97                 for e in env[k]]
98      # clang-cl wants INCLUDE to be ;-separated even on non-Windows,
99      # lld-link wants LIB to be ;-separated even on non-Windows.  Path gets :.
100      # The separator for INCLUDE here must match the one used in main() below.
101      sep = os.pathsep if k == 'PATH' else ';'
102      env[k] = sep.join(entries)
103    # PATH is a bit of a special case, it's in addition to the current PATH.
104    env['PATH'] = env['PATH'] + os.pathsep + os.environ['PATH']
105    # Augment with the current env to pick up TEMP and friends.
106    for k in os.environ:
107      if k not in env:
108        env[k] = os.environ[k]
109
110    varlines = []
111    for k in sorted(env.keys()):
112      varlines.append('%s=%s' % (str(k), str(env[k])))
113    variables = '\n'.join(varlines)
114
115    # Check that the json file contained the same environment as the .cmd file.
116    if sys.platform in ('win32', 'cygwin'):
117      script = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.cmd'))
118      assert _ExtractImportantEnvironment(variables) == \
119             _ExtractImportantEnvironment(_LoadEnvFromBat([script, '/' + cpu]))
120  else:
121    if 'GYP_MSVS_OVERRIDE_PATH' not in os.environ:
122      os.environ['GYP_MSVS_OVERRIDE_PATH'] = _DetectVisualStudioPath()
123    # We only support x64-hosted tools.
124    script_path = os.path.normpath(os.path.join(
125                                       os.environ['GYP_MSVS_OVERRIDE_PATH'],
126                                       'VC/vcvarsall.bat'))
127    if not os.path.exists(script_path):
128      raise Exception('%s is missing - make sure VC++ tools are installed.' %
129                      script_path)
130    args = [script_path, 'amd64_x86' if cpu == 'x86' else 'amd64']
131    variables = _LoadEnvFromBat(args)
132  return _ExtractImportantEnvironment(variables)
133
134
135def _FormatAsEnvironmentBlock(envvar_dict):
136  """Format as an 'environment block' directly suitable for CreateProcess.
137  Briefly this is a list of key=value\0, terminated by an additional \0. See
138  CreateProcess documentation for more details."""
139  block = ''
140  nul = '\0'
141  for key, value in envvar_dict.iteritems():
142    block += key + '=' + value + nul
143  block += nul
144  return block
145
146
147def _CopyTool(source_path):
148  """Copies the given tool to the current directory, including a warning not
149  to edit it."""
150  with open(source_path) as source_file:
151    tool_source = source_file.readlines()
152
153  # Add header and write it out to the current directory (which should be the
154  # root build dir). Don't write the file if a matching file already exists
155  # because that causes a cascade of unnecessary rebuilds.
156  match = False
157  contents = ''.join([tool_source[0],
158                      '# Generated by setup_toolchain.py do not edit.\n']
159                     + tool_source[1:])
160  out_path = 'gyp-win-tool'
161  try:
162    with open(out_path, 'rb') as read_tool_file:
163      existing_contents = read_tool_file.read()
164    if existing_contents == contents:
165      match = True
166  except:
167    pass
168  if not match:
169    with open(out_path, 'wb') as write_tool_file:
170      write_tool_file.write(contents)
171
172
173def main():
174  if len(sys.argv) != 7:
175    print('Usage setup_toolchain.py '
176          '<visual studio path> <win tool path> <win sdk path> '
177          '<runtime dirs> <target_cpu> <include prefix>')
178    sys.exit(2)
179  tool_source = sys.argv[2]
180  win_sdk_path = sys.argv[3]
181  runtime_dirs = sys.argv[4]
182  target_cpu = sys.argv[5]
183  include_prefix = sys.argv[6]
184
185  _CopyTool(tool_source)
186
187  cpus = ('x86', 'x64')
188  assert target_cpu in cpus
189  vc_bin_dir = ''
190  include = ''
191
192  # TODO(scottmg|goma): Do we need an equivalent of
193  # ninja_use_custom_environment_files?
194
195  for cpu in cpus:
196    # Extract environment variables for subprocesses.
197    env = _LoadToolchainEnv(cpu, win_sdk_path)
198    env['PATH'] = runtime_dirs + os.pathsep + env['PATH']
199
200    if cpu == target_cpu:
201      for path in env['PATH'].split(os.pathsep):
202        if os.path.exists(os.path.join(path, 'cl.exe')):
203          vc_bin_dir = os.path.realpath(path)
204          break
205      # The separator for INCLUDE here must match the one used in
206      # _LoadToolchainEnv() above.
207      include = ' '.join([include_prefix + p
208                          for p in env['INCLUDE'].split(';')])
209
210    env_block = _FormatAsEnvironmentBlock(env)
211    with open('environment.' + cpu, 'wb') as f:
212      f.write(env_block)
213
214    # Create a store app version of the environment.
215    if 'LIB' in env:
216      env['LIB']     = env['LIB']    .replace(r'\VC\LIB', r'\VC\LIB\STORE')
217    if 'LIBPATH' in env:
218      env['LIBPATH'] = env['LIBPATH'].replace(r'\VC\LIB', r'\VC\LIB\STORE')
219    env_block = _FormatAsEnvironmentBlock(env)
220    with open('environment.winrt_' + cpu, 'wb') as f:
221      f.write(env_block)
222
223  assert vc_bin_dir
224  assert '"' not in vc_bin_dir
225  print 'vc_bin_dir = "%s"' % vc_bin_dir
226  assert include
227  assert '"' not in include
228  print 'include_flags = "%s"' % include
229
230if __name__ == '__main__':
231  main()
232