Home | History | Annotate | Download | only in build
      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 
     10 import glob
     11 import gyp_environment
     12 import os
     13 import re
     14 import shlex
     15 import subprocess
     16 import string
     17 import sys
     18 import vs_toolchain
     19 
     20 script_dir = os.path.dirname(os.path.realpath(__file__))
     21 chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
     22 
     23 sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
     24 import gyp
     25 
     26 # Assume this file is in a one-level-deep subdirectory of the source root.
     27 SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     28 
     29 # Add paths so that pymod_do_main(...) can import files.
     30 sys.path.insert(1, os.path.join(chrome_src, 'build', 'android', 'gyp'))
     31 sys.path.insert(1, os.path.join(chrome_src, 'tools'))
     32 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers'))
     33 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit'))
     34 sys.path.insert(1, os.path.join(chrome_src, 'chrome', 'tools', 'build'))
     35 sys.path.insert(1, os.path.join(chrome_src, 'chromecast', '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 # On Windows, Psyco shortens warm runs of build/gyp_chromium by about
     45 # 20 seconds on a z600 machine with 12 GB of RAM, from 90 down to 70
     46 # seconds.  Conversely, memory usage of build/gyp_chromium with Psyco
     47 # maxes out at about 158 MB vs. 132 MB without it.
     48 #
     49 # Psyco uses native libraries, so we need to load a different
     50 # installation depending on which OS we are running under. It has not
     51 # been tested whether using Psyco on our Mac and Linux builds is worth
     52 # it (the GYP running time is a lot shorter, so the JIT startup cost
     53 # may not be worth it).
     54 if sys.platform == 'win32':
     55   try:
     56     sys.path.insert(0, os.path.join(chrome_src, 'third_party', 'psyco_win32'))
     57     import psyco
     58   except:
     59     psyco = None
     60 else:
     61   psyco = None
     62 
     63 
     64 def GetSupplementalFiles():
     65   """Returns a list of the supplemental files that are included in all GYP
     66   sources."""
     67   return glob.glob(os.path.join(chrome_src, '*', 'supplement.gypi'))
     68 
     69 
     70 def ProcessGypDefinesItems(items):
     71   """Converts a list of strings to a list of key-value pairs."""
     72   result = []
     73   for item in items:
     74     tokens = item.split('=', 1)
     75     # Some GYP variables have hyphens, which we don't support.
     76     if len(tokens) == 2:
     77       result += [(tokens[0], tokens[1])]
     78     else:
     79       # No value supplied, treat it as a boolean and set it. Note that we
     80       # use the string '1' here so we have a consistent definition whether
     81       # you do 'foo=1' or 'foo'.
     82       result += [(tokens[0], '1')]
     83   return result
     84 
     85 
     86 def GetGypVars(supplemental_files):
     87   """Returns a dictionary of all GYP vars."""
     88   # Find the .gyp directory in the user's home directory.
     89   home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None)
     90   if home_dot_gyp:
     91     home_dot_gyp = os.path.expanduser(home_dot_gyp)
     92   if not home_dot_gyp:
     93     home_vars = ['HOME']
     94     if sys.platform in ('cygwin', 'win32'):
     95       home_vars.append('USERPROFILE')
     96     for home_var in home_vars:
     97       home = os.getenv(home_var)
     98       if home != None:
     99         home_dot_gyp = os.path.join(home, '.gyp')
    100         if not os.path.exists(home_dot_gyp):
    101           home_dot_gyp = None
    102         else:
    103           break
    104 
    105   if home_dot_gyp:
    106     include_gypi = os.path.join(home_dot_gyp, "include.gypi")
    107     if os.path.exists(include_gypi):
    108       supplemental_files += [include_gypi]
    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 += [(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   vars_dict = dict(supp_items + env_items + cmdline_items)
    139   return vars_dict
    140 
    141 
    142 def GetOutputDirectory():
    143   """Returns the output directory that GYP will use."""
    144   # GYP generator flags from the command line. We can't use optparse since we
    145   # want to ignore all arguments other than "-G".
    146   needle = '-Goutput_dir='
    147   cmdline_input_items = []
    148   for item in sys.argv[1:]:
    149     if item.startswith(needle):
    150       return item[len(needle):]
    151 
    152   env_items = shlex.split(os.environ.get('GYP_GENERATOR_FLAGS', ''))
    153   needle = 'output_dir='
    154   for item in env_items:
    155     if item.startswith(needle):
    156       return item[len(needle):]
    157 
    158   return "out"
    159 
    160 
    161 def 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   # Always include common.gypi.
    182   AddInclude(os.path.join(script_dir, 'common.gypi'))
    183 
    184   # Optionally add supplemental .gypi files if present.
    185   for supplement in supplemental_files:
    186     AddInclude(supplement)
    187 
    188   return result
    189 
    190 
    191 if __name__ == '__main__':
    192   # Disabling garbage collection saves about 1 second out of 16 on a Linux
    193   # z620 workstation. Since this is a short-lived process it's not a problem to
    194   # leak a few cyclyc references in order to spare the CPU cycles for
    195   # scanning the heap.
    196   import gc
    197   gc.disable()
    198 
    199   args = sys.argv[1:]
    200 
    201   use_analyzer = len(args) and args[0] == '--analyzer'
    202   if use_analyzer:
    203     args.pop(0)
    204     os.environ['GYP_GENERATORS'] = 'analyzer'
    205     args.append('-Gconfig_path=' + args.pop(0))
    206     args.append('-Ganalyzer_output_path=' + args.pop(0))
    207 
    208   if int(os.environ.get('GYP_CHROMIUM_NO_ACTION', 0)):
    209     print 'Skipping gyp_chromium due to GYP_CHROMIUM_NO_ACTION env var.'
    210     sys.exit(0)
    211 
    212   # Use the Psyco JIT if available.
    213   if psyco:
    214     psyco.profile()
    215     print "Enabled Psyco JIT."
    216 
    217   # Fall back on hermetic python if we happen to get run under cygwin.
    218   # TODO(bradnelson): take this out once this issue is fixed:
    219   #    http://code.google.com/p/gyp/issues/detail?id=177
    220   if sys.platform == 'cygwin':
    221     import find_depot_tools
    222     depot_tools_path = find_depot_tools.add_depot_tools_to_path()
    223     python_dir = sorted(glob.glob(os.path.join(depot_tools_path,
    224                                                'python2*_bin')))[-1]
    225     env = os.environ.copy()
    226     env['PATH'] = python_dir + os.pathsep + env.get('PATH', '')
    227     p = subprocess.Popen(
    228        [os.path.join(python_dir, 'python.exe')] + sys.argv,
    229        env=env, shell=False)
    230     p.communicate()
    231     sys.exit(p.returncode)
    232 
    233   # This could give false positives since it doesn't actually do real option
    234   # parsing.  Oh well.
    235   gyp_file_specified = False
    236   for arg in args:
    237     if arg.endswith('.gyp'):
    238       gyp_file_specified = True
    239       break
    240 
    241   gyp_environment.SetEnvironment()
    242 
    243   # If we didn't get a file, check an env var, and then fall back to
    244   # assuming 'all.gyp' from the same directory as the script.
    245   if not gyp_file_specified:
    246     gyp_file = os.environ.get('CHROMIUM_GYP_FILE')
    247     if gyp_file:
    248       # Note that CHROMIUM_GYP_FILE values can't have backslashes as
    249       # path separators even on Windows due to the use of shlex.split().
    250       args.extend(shlex.split(gyp_file))
    251     else:
    252       args.append(os.path.join(script_dir, 'all.gyp'))
    253 
    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 the Mac, where a
    258   # violation 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 sys.platform not in ('darwin',):
    264     args.append('--no-circular-check')
    265 
    266   # We explicitly don't support the make gyp generator (crbug.com/348686). Be
    267   # nice and fail here, rather than choking in gyp.
    268   if re.search(r'(^|,|\s)make($|,|\s)', os.environ.get('GYP_GENERATORS', '')):
    269     print 'Error: make gyp generator not supported (check GYP_GENERATORS).'
    270     sys.exit(1)
    271 
    272   # If CHROMIUM_GYP_SYNTAX_CHECK is set to 1, it will invoke gyp with --check
    273   # to enfore syntax checking.
    274   syntax_check = os.environ.get('CHROMIUM_GYP_SYNTAX_CHECK')
    275   if syntax_check and int(syntax_check):
    276     args.append('--check')
    277 
    278   supplemental_includes = GetSupplementalFiles()
    279   gyp_vars_dict = GetGypVars(supplemental_includes)
    280 
    281   # TODO(dmikurube): Remove these checks and messages after a while.
    282   if ('linux_use_tcmalloc' in gyp_vars_dict or
    283       'android_use_tcmalloc' in gyp_vars_dict):
    284     print '*****************************************************************'
    285     print '"linux_use_tcmalloc" and "android_use_tcmalloc" are deprecated!'
    286     print '-----------------------------------------------------------------'
    287     print 'You specify "linux_use_tcmalloc" or "android_use_tcmalloc" in'
    288     print 'your GYP_DEFINES. Please switch them into "use_allocator" now.'
    289     print 'See http://crbug.com/345554 for the details.'
    290     print '*****************************************************************'
    291 
    292   # Automatically turn on crosscompile support for platforms that need it.
    293   # (The Chrome OS build sets CC_host / CC_target which implicitly enables
    294   # this mode.)
    295   if all(('ninja' in os.environ.get('GYP_GENERATORS', ''),
    296           gyp_vars_dict.get('OS') in ['android', 'ios'],
    297           'GYP_CROSSCOMPILE' not in os.environ)):
    298     os.environ['GYP_CROSSCOMPILE'] = '1'
    299   if gyp_vars_dict.get('OS') == 'android':
    300     args.append('--check')
    301 
    302   args.extend(
    303       ['-I' + i for i in additional_include_files(supplemental_includes, args)])
    304 
    305   args.extend(['-D', 'gyp_output_dir=' + GetOutputDirectory()])
    306 
    307   if not use_analyzer:
    308     print 'Updating projects from gyp files...'
    309     sys.stdout.flush()
    310 
    311   # Off we go...
    312   gyp_rc = gyp.main(args)
    313 
    314   if not use_analyzer:
    315     vs2013_runtime_dll_dirs = vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
    316     if vs2013_runtime_dll_dirs:
    317       x64_runtime, x86_runtime = vs2013_runtime_dll_dirs
    318       vs_toolchain.CopyVsRuntimeDlls(
    319         os.path.join(chrome_src, GetOutputDirectory()),
    320         (x86_runtime, x64_runtime))
    321 
    322   sys.exit(gyp_rc)
    323