1401f37e44120e8df19b7459141c4ba94de86a24
[platform/framework/web/crosswalk.git] / src / xwalk / gyp_xwalk
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 Crosswalk that adds some support for how GYP
8 # is invoked by Chromium beyond what can be done in the gclient hooks.
9
10 import glob
11 import os
12 import pipes
13 import shlex
14 import subprocess
15 import string
16 import sys
17
18 xwalk_dir = os.path.dirname(os.path.realpath(__file__))
19 chrome_src = os.path.abspath(os.path.join(xwalk_dir, os.pardir))
20
21 sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
22 import gyp
23
24 sys.path.insert(0, os.path.join(chrome_src, 'build'))
25 import gyp_helper
26 import landmine_utils
27
28 # Assume this file is in a one-level-deep subdirectory of the source root.
29 SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
30
31 # Add paths so that pymod_do_main(...) can import files.
32 sys.path.insert(1, os.path.join(chrome_src, 'tools'))
33 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers'))
34 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit'))
35 sys.path.insert(1, os.path.join(chrome_src, 'chrome', 'tools', 'build'))
36 sys.path.insert(1, os.path.join(chrome_src, 'native_client', 'build'))
37 sys.path.insert(1, os.path.join(chrome_src, 'native_client_sdk', 'src',
38     'build_tools'))
39 sys.path.insert(1, os.path.join(chrome_src, 'remoting', 'tools', 'build'))
40 sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'liblouis'))
41 sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'WebKit',
42     'Source', 'build', 'scripts'))
43
44 # FIXME(rakuco,joone): This is a workaround to allow the Tizen build to
45 # proceed, as we use make there and GN isn't involved in this case (otherwise
46 # we run into problems like GN always looking for GTK+ dependencies).
47 if os.environ.get('GYP_GENERATORS') != 'make':
48   import find_depot_tools
49
50 # On Windows, Psyco shortens warm runs of build/gyp_chromium by about
51 # 20 seconds on a z600 machine with 12 GB of RAM, from 90 down to 70
52 # seconds.  Conversely, memory usage of build/gyp_chromium with Psyco
53 # maxes out at about 158 MB vs. 132 MB without it.
54 #
55 # Psyco uses native libraries, so we need to load a different
56 # installation depending on which OS we are running under. It has not
57 # been tested whether using Psyco on our Mac and Linux builds is worth
58 # it (the GYP running time is a lot shorter, so the JIT startup cost
59 # may not be worth it).
60 if sys.platform == 'win32':
61   try:
62     sys.path.insert(0, os.path.join(chrome_src, 'third_party', 'psyco_win32'))
63     import psyco
64   except:
65     psyco = None
66 else:
67   psyco = None
68
69
70 def GetSupplementalFiles():
71   """Returns a list of the supplemental files that are included in all GYP
72   sources."""
73   return glob.glob(os.path.join(chrome_src, '*', 'supplement.gypi'))
74
75
76 def FormatKeyForGN(key):
77   """Returns the given GYP key reformatted for GN.
78
79   GYP dictionary keys can be almost anything, but in GN they are identifiers
80   and must follow the same rules. This reformats such keys to be valid GN
81   identifiers."""
82   return ''.join([c if c in string.ascii_letters else '_' for c in key])
83
84
85 def EscapeStringForGN(s):
86   """Converts a string to a GN string literal."""
87   # Escape $ characters which have special meaning to GN.
88   return '"' + s.replace('$', '\\$').replace('"', '\\"') + '"'
89
90
91 def ProcessGypDefinesItems(items):
92   """Converts a list of strings to a list of key-value pairs."""
93   result = []
94   for item in items:
95     tokens = item.split('=', 1)
96     # Some GYP variables have hyphens, which we don't support.
97     key = FormatKeyForGN(tokens[0])
98     if len(tokens) == 2:
99       result += [(key, tokens[1])]
100     else:
101       # No value supplied, treat it as a boolean and set it. Note that we
102       # use the string '1' here so we have a consistent definition whether
103       # you do 'foo=1' or 'foo'.
104       result += [(key, '1')]
105   return result
106
107 def GetGypVarsForGN(supplemental_files):
108   """Returns a dictionary of all GYP vars that we will be passing to GN."""
109
110   # GYP defines from the supplemental.gypi files.
111   supp_items = []
112   for supplement in supplemental_files:
113     with open(supplement, 'r') as f:
114       try:
115         file_data = eval(f.read(), {'__builtins__': None}, None)
116       except SyntaxError, e:
117         e.filename = os.path.abspath(supplement)
118         raise
119       variables = file_data.get('variables', [])
120       for v in variables:
121         supp_items += [(FormatKeyForGN(v), str(variables[v]))]
122
123   # GYP defines from the environment.
124   env_items = ProcessGypDefinesItems(
125       shlex.split(os.environ.get('GYP_DEFINES', '')))
126
127   # GYP defines from the command line. We can't use optparse since we want
128   # to ignore all arguments other than "-D".
129   cmdline_input_items = []
130   for i in range(len(sys.argv))[1:]:
131     if sys.argv[i].startswith('-D'):
132       if sys.argv[i] == '-D' and i + 1 < len(sys.argv):
133         cmdline_input_items += [sys.argv[i + 1]]
134       elif len(sys.argv[i]) > 2:
135         cmdline_input_items += [sys.argv[i][2:]]
136   cmdline_items = ProcessGypDefinesItems(cmdline_input_items)
137
138   return dict(supp_items + env_items + cmdline_items)
139
140 def 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 def GetArgsStringForGN(supplemental_files):
159   """Returns the args to pass to GN.
160   Based on a subset of the GYP variables that have been rewritten a bit."""
161
162   # Find the .gyp directory in the user's home directory.
163   home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
164   if home_dot_gyp:
165     home_dot_gyp = os.path.expanduser(home_dot_gyp)
166   if not home_dot_gyp:
167     home_vars = ['HOME']
168     if sys.platform in ('cygwin', 'win32'):
169       home_vars.append('USERPROFILE')
170     for home_var in home_vars:
171       home = os.getenv(home_var)
172       if home != None:
173         home_dot_gyp = os.path.join(home, '.gyp')
174         if not os.path.exists(home_dot_gyp):
175           home_dot_gyp = None
176         else:
177           break
178
179   if home_dot_gyp:
180     include_gypi = os.path.join(home_dot_gyp, "include.gypi")
181     if os.path.exists(include_gypi):
182       supplemental_files += [include_gypi]
183
184   vars_dict = GetGypVarsForGN(supplemental_files)
185   gn_args = ''
186
187   # Note: These are the additional flags passed to various builds by builders
188   # on the main waterfall. We'll probably need to add these at some point:
189   #   mac_strip_release=1         http://crbug.com/330301
190   #   linux_dump_symbols=0        http://crbug.com/330300
191   #   host_os=linux  Probably can skip, GN knows the host OS.
192   #   order_text_section=<path>   http://crbug.com/330299
193   #   chromium_win_pch=0          http://crbug.com/297678
194   #   chromium_ios_signing=0      http://crbug.com/330302
195   #   linux_use_tcmalloc=0        http://crbug.com/330303
196   #   release_extra_flags=...     http://crbug.com/330305
197
198   # These tuples of (key, value, gn_arg_string) use the gn_arg_string for
199   # gn when the key is set to the given value in the GYP arguments.
200   remap_cases = [
201       ('android_webview_build', '1', 'is_android_webview_build=true'),
202       ('branding', 'Chrome', 'is_chrome_branded=true'),
203       ('build_for_tool', 'drmemory', 'disable_iterator_debugging=true'),
204       ('build_for_tool', 'tsan', 'disable_iterator_debugging=true'),
205       ('buildtype', 'Official', 'is_official_build=true'),
206       ('component', 'shared_library', 'is_component_build=true'),
207       ('clang', '1', 'is_clang=true'),
208       ('clang_use_chrome_plugins', '0', 'clang_use_chrome_plugins=false'),
209       ('disable_glibcxx_debug', '1', 'disable_iterator_debugging=true'),
210       ('target_arch', 'ia32', 'cpu_arch="x86"'),
211       ('target_arch', 'x64', 'cpu_arch="x64" force_win64=true'),
212       ('target_arch', 'arm', 'cpu_arch="arm"'),
213       ('target_arch', 'mipsel', 'cpu_arch="mipsel"'),
214       ('fastbuild', '0', 'symbol_level=2'),
215       ('fastbuild', '1', 'symbol_level=1'),
216       ('fastbuild', '2', 'symbol_level=0'),
217       ('OS', 'ios', 'os="ios"'),
218       ('OS', 'android', 'os="android"'),
219       ('chromeos', '1', 'os="chromeos"'),
220       ('use_aura', '1', 'use_aura=true'),
221       ('use_goma', '1', 'use_goma=true'),
222       ('asan', '1', 'is_asan=true'),
223       ('lsan', '1', 'is_lsan=true'),
224       ('msan', '1', 'is_msan=true'),
225       ('tsan', '1', 'is_tsan=true'),
226   ]
227   for i in remap_cases:
228     if i[0] in vars_dict and vars_dict[i[0]] == i[1]:
229       gn_args += ' ' + i[2]
230
231   # These string arguments get passed directly as GN strings.
232   for v in ['android_src', 'windows_sdk_path', 'arm_float_abi']:
233     if v in vars_dict:
234       gn_args += ' ' + v + '=' + EscapeStringForGN(vars_dict[v])
235
236   # gomadir is renamed goma_dir in the GN build.
237   if 'gomadir' in vars_dict:
238     gn_args += ' goma_dir=%s' % EscapeStringForGN(vars_dict['gomadir'])
239
240   # These arguments get passed directly as integers (avoiding the quoting and
241   # escaping of the string ones above).
242   for v in ['arm_version']:
243     if v in vars_dict:
244       gn_args += ' %s=%s' % (v, vars_dict[v])
245
246   # Some other flags come from GYP environment variables.
247   gyp_msvs_version = os.environ.get('GYP_MSVS_VERSION', '')
248   if gyp_msvs_version:
249     gn_args += ' visual_studio_version=' + EscapeStringForGN(gyp_msvs_version)
250   gyp_msvs_override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH', '')
251   if gyp_msvs_override_path:
252     gn_args += ' visual_studio_path=' + \
253         EscapeStringForGN(gyp_msvs_override_path)
254
255   # Set the GYP flag so BUILD files know they're being invoked in GYP mode.
256   gn_args += ' is_gyp=true'
257
258   gyp_outdir = GetOutputDirectory()
259   gn_args += ' gyp_output_dir=\"%s\"' % gyp_outdir
260
261   return gn_args.strip()
262
263
264 def additional_include_files(supplemental_files, args=[]):
265   """
266   Returns a list of additional (.gypi) files to include, without duplicating
267   ones that are already specified on the command line. The list of supplemental
268   include files is passed in as an argument.
269   """
270   # Determine the include files specified on the command line.
271   # This doesn't cover all the different option formats you can use,
272   # but it's mainly intended to avoid duplicating flags on the automatic
273   # makefile regeneration which only uses this format.
274   specified_includes = set()
275   for arg in args:
276     if arg.startswith('-I') and len(arg) > 2:
277       specified_includes.add(os.path.realpath(arg[2:]))
278
279   result = []
280   def AddInclude(path):
281     if os.path.realpath(path) not in specified_includes:
282       result.append(path)
283
284   # Include xwalk common.gypi to effect chromium source tree.
285   AddInclude(os.path.join(xwalk_dir, 'build', 'common.gypi'))
286
287   # Always include common.gypi.
288   AddInclude(os.path.join(chrome_src, 'build', 'common.gypi'))
289
290   # Optionally add supplemental .gypi files if present.
291   for supplement in supplemental_files:
292     AddInclude(supplement)
293
294   return result
295
296
297 def RunGN(supplemental_includes):
298   """Runs GN, returning True if it succeeded, printing an error and returning
299   false if not."""
300
301   # The binaries in platform-specific subdirectories in src/tools/gn/bin.
302   gnpath = SRC_DIR + '/tools/gn/bin/'
303   if sys.platform in ('cygwin', 'win32'):
304     gnpath += 'win/gn.exe'
305   elif sys.platform.startswith('linux'):
306     # On Linux we have 32-bit and 64-bit versions.
307     if subprocess.check_output(["getconf", "LONG_BIT"]).find("64") >= 0:
308       gnpath += 'linux/gn'
309     else:
310       gnpath += 'linux/gn32'
311   elif sys.platform == 'darwin':
312     gnpath += 'mac/gn'
313   else:
314     print 'Unknown platform for GN: ', sys.platform
315     return False
316
317   print 'Generating gyp files from GN...'
318
319   # Need to pass both the source root (the bots don't run this command from
320   # within the source tree) as well as set the is_gyp value so the BUILD files
321   # to know they're being run under GYP.
322   args = [gnpath, 'gyp', '-q',
323           '--root=' + chrome_src,
324           '--args=' + GetArgsStringForGN(supplemental_includes),
325           '--output=//' + GetOutputDirectory() + '/gn_build/']
326   return subprocess.call(args) == 0
327
328
329 if __name__ == '__main__':
330   args = sys.argv[1:]
331
332   if int(os.environ.get('GYP_CHROMIUM_NO_ACTION', 0)):
333     print 'Skipping gyp_chromium due to GYP_CHROMIUM_NO_ACTION env var.'
334     sys.exit(0)
335
336   # Support external media types capability such as MP4/MP3.
337   args = list(set(args))
338   delist = []
339   ip_media_codecs = False # Default: no third-party codecs be build in.
340   for arg in args:
341     if arg.startswith('-Dproprietary_codecs') or arg.startswith('-Dffmpeg_branding'):
342       continue
343     elif arg == '-Dmediacodecs_EULA=1':
344       ip_media_codecs = True  # Exception: mediacodecs_EULA be enabled.
345     else:
346       delist.append(arg)
347
348   args = delist
349   args.append('-Dproprietary_codecs=1')
350
351   # Triggering media playback dynamically with third-party codecs by owner.
352   if ip_media_codecs == True:
353       args.append('-Dffmpeg_branding=Chrome')
354
355   # Enable Web Audio by default on Android x86
356   if landmine_utils.platform() == 'android':
357     args.append('-Duse_openmax_dl_fft=1')
358
359   # Enable Aura by default on all platforms except Android
360   if landmine_utils.platform() != 'android' and landmine_utils.platform() != 'mac':
361     args.append('-Duse_aura=1')
362
363   if landmine_utils.platform() == 'android':
364     args.append('-Dnotifications=1')
365     args.append('-Drelease_unwind_tables=0')
366
367   # Use the Psyco JIT if available.
368   if psyco:
369     psyco.profile()
370     print "Enabled Psyco JIT."
371
372   # Fall back on hermetic python if we happen to get run under cygwin.
373   # TODO(bradnelson): take this out once this issue is fixed:
374   #    http://code.google.com/p/gyp/issues/detail?id=177
375   if sys.platform == 'cygwin':
376     depot_tools_path = find_depot_tools.add_depot_tools_to_path()
377     python_dir = sorted(glob.glob(os.path.join(depot_tools_path,
378                                                'python2*_bin')))[-1]
379     env = os.environ.copy()
380     env['PATH'] = python_dir + os.pathsep + env.get('PATH', '')
381     p = subprocess.Popen(
382        [os.path.join(python_dir, 'python.exe')] + sys.argv,
383        env=env, shell=False)
384     p.communicate()
385     sys.exit(p.returncode)
386
387   gyp_helper.apply_chromium_gyp_env()
388
389   # This could give false positives since it doesn't actually do real option
390   # parsing.  Oh well.
391   gyp_file_specified = False
392   for arg in args:
393     if arg.endswith('.gyp'):
394       gyp_file_specified = True
395       break
396
397   # If we didn't get a file, check an env var, and then fall back to
398   # assuming 'all.gyp' from the same directory as the script.
399   if not gyp_file_specified:
400     gyp_file = os.environ.get('CHROMIUM_GYP_FILE')
401     if gyp_file:
402       # Note that CHROMIUM_GYP_FILE values can't have backslashes as
403       # path separators even on Windows due to the use of shlex.split().
404       args.extend(shlex.split(gyp_file))
405     else:
406       args.append(os.path.join(xwalk_dir, 'xwalk.gyp'))
407
408   # There shouldn't be a circular dependency relationship between .gyp files,
409   # but in Chromium's .gyp files, on non-Mac platforms, circular relationships
410   # currently exist.  The check for circular dependencies is currently
411   # bypassed on other platforms, but is left enabled on the Mac, where a
412   # violation of the rule causes Xcode to misbehave badly.
413   # TODO(mark): Find and kill remaining circular dependencies, and remove this
414   # option.  http://crbug.com/35878.
415   # TODO(tc): Fix circular dependencies in ChromiumOS then add linux2 to the
416   # list.
417   # TODO(tmpsantos): Make runtime a proper module and enable the circular check
418   # back for Mac.
419   args.append('--no-circular-check')
420
421   # Default to ninja on linux and windows, but only if no generator has
422   # explicitly been set.
423   # Also default to ninja on mac, but only when not building chrome/ios.
424   # . -f / --format has precedence over the env var, no need to check for it
425   # . set the env var only if it hasn't been set yet
426   # . chromium.gyp_env has been applied to os.environ at this point already
427   if sys.platform.startswith('linux') and not os.environ.get('GYP_GENERATORS'):
428     os.environ['GYP_GENERATORS'] = 'ninja'
429   if sys.platform.startswith('win') and not os.environ.get('GYP_GENERATORS'):
430     os.environ['GYP_GENERATORS'] = 'ninja'
431   elif sys.platform == 'darwin' and not os.environ.get('GYP_GENERATORS') and \
432       not 'OS=ios' in os.environ.get('GYP_DEFINES', []):
433     os.environ['GYP_GENERATORS'] = 'ninja'
434
435   # If using ninja on windows, and the automatic toolchain has been installed
436   # by depot_tools, then use it.
437   if (sys.platform in ('win32', 'cygwin') and
438       os.environ.get('GYP_GENERATORS') == 'ninja'):
439     depot_tools_path = find_depot_tools.add_depot_tools_to_path()
440     toolchain = os.path.normpath(os.path.join(
441         depot_tools_path, 'win_toolchain', 'vs2013_files'))
442     if os.path.isdir(toolchain):
443       os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
444       os.environ['GYP_MSVS_VERSION'] = '2013'
445       # We need to make sure windows_sdk_path is set to the automated
446       # toolchain values in GYP_DEFINES, but don't want to override any other
447       # values there.
448       gyp_defines_dict = gyp.NameValueListToDict(gyp.ShlexEnv('GYP_DEFINES'))
449       win8sdk = os.path.join(toolchain, 'win8sdk')
450       gyp_defines_dict['windows_sdk_path'] = win8sdk
451       os.environ['WINDOWSSDKDIR'] = win8sdk
452       os.environ['GYP_DEFINES'] = ' '.join('%s=%s' % (k, pipes.quote(str(v)))
453           for k, v in gyp_defines_dict.iteritems())
454       # Include the VS runtime in the PATH in case it's not machine-installed.
455       runtime_path = ';'.join(os.path.normpath(os.path.join(toolchain, s))
456                               for s in ('sys64', 'sys32'))
457       os.environ['PATH'] = runtime_path + ';' + os.environ['PATH']
458       print('Using automatic toolchain in %s.' % toolchain)
459
460   # If CHROMIUM_GYP_SYNTAX_CHECK is set to 1, it will invoke gyp with --check
461   # to enfore syntax checking.
462   syntax_check = os.environ.get('CHROMIUM_GYP_SYNTAX_CHECK')
463   if syntax_check and int(syntax_check):
464     args.append('--check')
465
466   supplemental_includes = GetSupplementalFiles()
467
468   # FIXME(rakuco,joone): This is a workaround to allow the Tizen build to
469   # proceed, as we use make there and GN isn't involved in this case (otherwise
470   # we run into problems like GN always looking for GTK+ dependencies).
471   if os.environ.get('GYP_GENERATORS') != 'make':
472     if not RunGN(supplemental_includes):
473       sys.exit(1)
474
475   args.extend(
476       ['-I' + i for i in additional_include_files(supplemental_includes, args)])
477
478   args.extend(['-D', 'gyp_output_dir=' + GetOutputDirectory()])
479
480   print 'Updating projects from gyp files...'
481   sys.stdout.flush()
482
483   # Off we go...
484   sys.exit(gyp.main(args))