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