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_environment 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, 'build', 'android', 'gyp')) 31sys.path.insert(1, os.path.join(chrome_src, 'tools')) 32sys.path.insert(1, os.path.join(chrome_src, 'tools', 'generate_shim_headers')) 33sys.path.insert(1, os.path.join(chrome_src, 'tools', 'grit')) 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, 'native_client', 'build')) 37sys.path.insert(1, os.path.join(chrome_src, 'native_client_sdk', 'src', 38 'build_tools')) 39sys.path.insert(1, os.path.join(chrome_src, 'remoting', 'tools', 'build')) 40sys.path.insert(1, os.path.join(chrome_src, 'third_party', 'liblouis')) 41sys.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). 54if 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 60else: 61 psyco = None 62 63 64def 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 70def 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 86def 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 142def 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 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 # 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 191if __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