• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# Copyright (c) 2012 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
7# This script is wrapper for Chromium that adds some support for how GYP
8# is invoked by Chromium beyond what can be done in the gclient hooks.
9
10import glob
11import gyp_helper
12import os
13import re
14import shlex
15import subprocess
16import string
17import sys
18import vs_toolchain
19
20script_dir = os.path.dirname(os.path.realpath(__file__))
21chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
22
23sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
24import gyp
25
26# Assume this file is in a one-level-deep subdirectory of the source root.
27SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
28
29# Add paths so that pymod_do_main(...) can import files.
30sys.path.insert(1, os.path.join(chrome_src, 'tools'))
31sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers'))
32sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit'))
33sys.path.insert(1, os.path.join(chrome_src, 'chrome', 'tools', 'build'))
34sys.path.insert(1, os.path.join(chrome_src, 'native_client', 'build'))
35sys.path.insert(1, os.path.join(chrome_src, 'native_client_sdk', 'src',
36    'build_tools'))
37sys.path.insert(1, os.path.join(chrome_src, 'remoting', 'tools', 'build'))
38sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'liblouis'))
39sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'WebKit',
40    'Source', 'build', 'scripts'))
41
42# On Windows, Psyco shortens warm runs of build/gyp_chromium by about
43# 20 seconds on a z600 machine with 12 GB of RAM, from 90 down to 70
44# seconds.  Conversely, memory usage of build/gyp_chromium with Psyco
45# maxes out at about 158 MB vs. 132 MB without it.
46#
47# Psyco uses native libraries, so we need to load a different
48# installation depending on which OS we are running under. It has not
49# been tested whether using Psyco on our Mac and Linux builds is worth
50# it (the GYP running time is a lot shorter, so the JIT startup cost
51# may not be worth it).
52if sys.platform == 'win32':
53  try:
54    sys.path.insert(0, os.path.join(chrome_src, 'third_party', 'psyco_win32'))
55    import psyco
56  except:
57    psyco = None
58else:
59  psyco = None
60
61
62def GetSupplementalFiles():
63  """Returns a list of the supplemental files that are included in all GYP
64  sources."""
65  return glob.glob(os.path.join(chrome_src, '*', 'supplement.gypi'))
66
67
68def ProcessGypDefinesItems(items):
69  """Converts a list of strings to a list of key-value pairs."""
70  result = []
71  for item in items:
72    tokens = item.split('=', 1)
73    # Some GYP variables have hyphens, which we don't support.
74    if len(tokens) == 2:
75      result += [(tokens[0], tokens[1])]
76    else:
77      # No value supplied, treat it as a boolean and set it. Note that we
78      # use the string '1' here so we have a consistent definition whether
79      # you do 'foo=1' or 'foo'.
80      result += [(tokens[0], '1')]
81  return result
82
83
84def GetGypVars(supplemental_files):
85  """Returns a dictionary of all GYP vars."""
86  # Find the .gyp directory in the user's home directory.
87  home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
88  if home_dot_gyp:
89    home_dot_gyp = os.path.expanduser(home_dot_gyp)
90  if not home_dot_gyp:
91    home_vars = ['HOME']
92    if sys.platform in ('cygwin', 'win32'):
93      home_vars.append('USERPROFILE')
94    for home_var in home_vars:
95      home = os.getenv(home_var)
96      if home != None:
97        home_dot_gyp = os.path.join(home, '.gyp')
98        if not os.path.exists(home_dot_gyp):
99          home_dot_gyp = None
100        else:
101          break
102
103  if home_dot_gyp:
104    include_gypi = os.path.join(home_dot_gyp, "include.gypi")
105    if os.path.exists(include_gypi):
106      supplemental_files += [include_gypi]
107
108  # GYP defines from the supplemental.gypi files.
109  supp_items = []
110  for supplement in supplemental_files:
111    with open(supplement, 'r') as f:
112      try:
113        file_data = eval(f.read(), {'__builtins__': None}, None)
114      except SyntaxError, e:
115        e.filename = os.path.abspath(supplement)
116        raise
117      variables = file_data.get('variables', [])
118      for v in variables:
119        supp_items += [(v, str(variables[v]))]
120
121  # GYP defines from the environment.
122  env_items = ProcessGypDefinesItems(
123      shlex.split(os.environ.get('GYP_DEFINES', '')))
124
125  # GYP defines from the command line. We can't use optparse since we want
126  # to ignore all arguments other than "-D".
127  cmdline_input_items = []
128  for i in range(len(sys.argv))[1:]:
129    if sys.argv[i].startswith('-D'):
130      if sys.argv[i] == '-D' and i + 1 < len(sys.argv):
131        cmdline_input_items += [sys.argv[i + 1]]
132      elif len(sys.argv[i]) > 2:
133        cmdline_input_items += [sys.argv[i][2:]]
134  cmdline_items = ProcessGypDefinesItems(cmdline_input_items)
135
136  vars_dict = dict(supp_items + env_items + cmdline_items)
137  return vars_dict
138
139
140def GetOutputDirectory():
141  """Returns the output directory that GYP will use."""
142  # GYP generator flags from the command line. We can't use optparse since we
143  # want to ignore all arguments other than "-G".
144  needle = '-Goutput_dir='
145  cmdline_input_items = []
146  for item in sys.argv[1:]:
147    if item.startswith(needle):
148      return item[len(needle):]
149
150  env_items = shlex.split(os.environ.get('GYP_GENERATOR_FLAGS', ''))
151  needle = 'output_dir='
152  for item in env_items:
153    if item.startswith(needle):
154      return item[len(needle):]
155
156  return "out"
157
158
159def additional_include_files(supplemental_files, args=[]):
160  """
161  Returns a list of additional (.gypi) files to include, without duplicating
162  ones that are already specified on the command line. The list of supplemental
163  include files is passed in as an argument.
164  """
165  # Determine the include files specified on the command line.
166  # This doesn't cover all the different option formats you can use,
167  # but it's mainly intended to avoid duplicating flags on the automatic
168  # makefile regeneration which only uses this format.
169  specified_includes = set()
170  for arg in args:
171    if arg.startswith('-I') and len(arg) > 2:
172      specified_includes.add(os.path.realpath(arg[2:]))
173
174  result = []
175  def AddInclude(path):
176    if os.path.realpath(path) not in specified_includes:
177      result.append(path)
178
179  # Always include common.gypi.
180  AddInclude(os.path.join(script_dir, 'common.gypi'))
181
182  # Optionally add supplemental .gypi files if present.
183  for supplement in supplemental_files:
184    AddInclude(supplement)
185
186  return result
187
188
189if __name__ == '__main__':
190  args = sys.argv[1:]
191
192  if int(os.environ.get('GYP_CHROMIUM_NO_ACTION', 0)):
193    # Check for landmines (reasons to clobber the build) in any case.
194    print 'Running build/landmines.py...'
195    subprocess.check_call(
196        [sys.executable, os.path.join(script_dir, 'landmines.py')])
197    print 'Skipping gyp_chromium due to GYP_CHROMIUM_NO_ACTION env var.'
198    sys.exit(0)
199
200  # Use the Psyco JIT if available.
201  if psyco:
202    psyco.profile()
203    print "Enabled Psyco JIT."
204
205  # Fall back on hermetic python if we happen to get run under cygwin.
206  # TODO(bradnelson): take this out once this issue is fixed:
207  #    http://code.google.com/p/gyp/issues/detail?id=177
208  if sys.platform == 'cygwin':
209    import find_depot_tools
210    depot_tools_path = find_depot_tools.add_depot_tools_to_path()
211    python_dir = sorted(glob.glob(os.path.join(depot_tools_path,
212                                               'python2*_bin')))[-1]
213    env = os.environ.copy()
214    env['PATH'] = python_dir + os.pathsep + env.get('PATH', '')
215    p = subprocess.Popen(
216       [os.path.join(python_dir, 'python.exe')] + sys.argv,
217       env=env, shell=False)
218    p.communicate()
219    sys.exit(p.returncode)
220
221  gyp_helper.apply_chromium_gyp_env()
222
223  # This could give false positives since it doesn't actually do real option
224  # parsing.  Oh well.
225  gyp_file_specified = False
226  for arg in args:
227    if arg.endswith('.gyp'):
228      gyp_file_specified = True
229      break
230
231  # If we didn't get a file, check an env var, and then fall back to
232  # assuming 'all.gyp' from the same directory as the script.
233  if not gyp_file_specified:
234    gyp_file = os.environ.get('CHROMIUM_GYP_FILE')
235    if gyp_file:
236      # Note that CHROMIUM_GYP_FILE values can't have backslashes as
237      # path separators even on Windows due to the use of shlex.split().
238      args.extend(shlex.split(gyp_file))
239    else:
240      args.append(os.path.join(script_dir, 'all.gyp'))
241
242  # There shouldn't be a circular dependency relationship between .gyp files,
243  # but in Chromium's .gyp files, on non-Mac platforms, circular relationships
244  # currently exist.  The check for circular dependencies is currently
245  # bypassed on other platforms, but is left enabled on the Mac, where a
246  # violation of the rule causes Xcode to misbehave badly.
247  # TODO(mark): Find and kill remaining circular dependencies, and remove this
248  # option.  http://crbug.com/35878.
249  # TODO(tc): Fix circular dependencies in ChromiumOS then add linux2 to the
250  # list.
251  if sys.platform not in ('darwin',):
252    args.append('--no-circular-check')
253
254  # We explicitly don't support the make gyp generator (crbug.com/348686). Be
255  # nice and fail here, rather than choking in gyp.
256  if re.search(r'(^|,|\s)make($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
257    print 'Error: make gyp generator not supported (check GYP_GENERATORS).'
258    sys.exit(1)
259
260  # Default to ninja on linux and windows, but only if no generator has
261  # explicitly been set.
262  # Also default to ninja on mac, but only when not building chrome/ios.
263  # . -f / --format has precedence over the env var, no need to check for it
264  # . set the env var only if it hasn't been set yet
265  # . chromium.gyp_env has been applied to os.environ at this point already
266  if sys.platform.startswith(('linux', 'win', 'freebsd')) and \
267      not os.environ.get('GYP_GENERATORS'):
268    os.environ['GYP_GENERATORS'] = 'ninja'
269  elif sys.platform == 'darwin' and not os.environ.get('GYP_GENERATORS') and \
270      not 'OS=ios' in os.environ.get('GYP_DEFINES', []):
271    os.environ['GYP_GENERATORS'] = 'ninja'
272
273  vs2013_runtime_dll_dirs = vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
274
275  # If CHROMIUM_GYP_SYNTAX_CHECK is set to 1, it will invoke gyp with --check
276  # to enfore syntax checking.
277  syntax_check = os.environ.get('CHROMIUM_GYP_SYNTAX_CHECK')
278  if syntax_check and int(syntax_check):
279    args.append('--check')
280
281  supplemental_includes = GetSupplementalFiles()
282  gyp_vars_dict = GetGypVars(supplemental_includes)
283
284  # TODO(dmikurube): Remove these checks and messages after a while.
285  if ('linux_use_tcmalloc' in gyp_vars_dict or
286      'android_use_tcmalloc' in gyp_vars_dict):
287    print '*****************************************************************'
288    print '"linux_use_tcmalloc" and "android_use_tcmalloc" are deprecated!'
289    print '-----------------------------------------------------------------'
290    print 'You specify "linux_use_tcmalloc" or "android_use_tcmalloc" in'
291    print 'your GYP_DEFINES. Please switch them into "use_allocator" now.'
292    print 'See http://crbug.com/345554 for the details.'
293    print '*****************************************************************'
294
295  # Automatically turn on crosscompile support for platforms that need it.
296  # (The Chrome OS build sets CC_host / CC_target which implicitly enables
297  # this mode.)
298  if all(('ninja' in os.environ.get('GYP_GENERATORS', ''),
299          gyp_vars_dict.get('OS') in ['android', 'ios'],
300          'GYP_CROSSCOMPILE' not in os.environ)):
301    os.environ['GYP_CROSSCOMPILE'] = '1'
302  if gyp_vars_dict.get('OS') == 'android':
303    args.append('--check')
304
305  args.extend(
306      ['-I' + i for i in additional_include_files(supplemental_includes, args)])
307
308  args.extend(['-D', 'gyp_output_dir=' + GetOutputDirectory()])
309
310  print 'Updating projects from gyp files...'
311  sys.stdout.flush()
312
313  # Off we go...
314  gyp_rc = gyp.main(args)
315
316  # Check for landmines (reasons to clobber the build). This must be run here,
317  # rather than a separate runhooks step so that any environment modifications
318  # from above are picked up.
319  print 'Running build/landmines.py...'
320  subprocess.check_call(
321      [sys.executable, os.path.join(script_dir, 'landmines.py')])
322
323  if vs2013_runtime_dll_dirs:
324    x64_runtime, x86_runtime = vs2013_runtime_dll_dirs
325    vs_toolchain.CopyVsRuntimeDlls(
326        os.path.join(chrome_src, GetOutputDirectory()),
327        (x86_runtime, x64_runtime))
328
329  sys.exit(gyp_rc)
330