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_helper
     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, 'tools'))
     31 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers'))
     32 sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit'))
     33 sys.path.insert(1, os.path.join(chrome_src, 'chrome', 'tools', 'build'))
     34 sys.path.insert(1, os.path.join(chrome_src, 'native_client', 'build'))
     35 sys.path.insert(1, os.path.join(chrome_src, 'native_client_sdk', 'src',
     36     'build_tools'))
     37 sys.path.insert(1, os.path.join(chrome_src, 'remoting', 'tools', 'build'))
     38 sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'liblouis'))
     39 sys.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).
     52 if 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
     58 else:
     59   psyco = None
     60 
     61 
     62 def 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 
     68 def 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 
     84 def 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 
    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 
    159 def 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 
    189 if __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