1#!/usr/bin/env python 2 3# Copyright (c) 2012 Google Inc. 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 7import copy 8import gyp.input 9import optparse 10import os.path 11import re 12import shlex 13import sys 14import traceback 15from gyp.common import GypError 16 17# Default debug modes for GYP 18debug = {} 19 20# List of "official" debug modes, but you can use anything you like. 21DEBUG_GENERAL = 'general' 22DEBUG_VARIABLES = 'variables' 23DEBUG_INCLUDES = 'includes' 24 25 26def DebugOutput(mode, message, *args): 27 if 'all' in gyp.debug or mode in gyp.debug: 28 ctx = ('unknown', 0, 'unknown') 29 try: 30 f = traceback.extract_stack(limit=2) 31 if f: 32 ctx = f[0][:3] 33 except: 34 pass 35 if args: 36 message %= args 37 print '%s:%s:%d:%s %s' % (mode.upper(), os.path.basename(ctx[0]), 38 ctx[1], ctx[2], message) 39 40def FindBuildFiles(): 41 extension = '.gyp' 42 files = os.listdir(os.getcwd()) 43 build_files = [] 44 for file in files: 45 if file.endswith(extension): 46 build_files.append(file) 47 return build_files 48 49 50def Load(build_files, format, default_variables={}, 51 includes=[], depth='.', params=None, check=False, 52 circular_check=True): 53 """ 54 Loads one or more specified build files. 55 default_variables and includes will be copied before use. 56 Returns the generator for the specified format and the 57 data returned by loading the specified build files. 58 """ 59 if params is None: 60 params = {} 61 62 flavor = None 63 if '-' in format: 64 format, params['flavor'] = format.split('-', 1) 65 66 default_variables = copy.copy(default_variables) 67 68 # Default variables provided by this program and its modules should be 69 # named WITH_CAPITAL_LETTERS to provide a distinct "best practice" namespace, 70 # avoiding collisions with user and automatic variables. 71 default_variables['GENERATOR'] = format 72 73 # Format can be a custom python file, or by default the name of a module 74 # within gyp.generator. 75 if format.endswith('.py'): 76 generator_name = os.path.splitext(format)[0] 77 path, generator_name = os.path.split(generator_name) 78 79 # Make sure the path to the custom generator is in sys.path 80 # Don't worry about removing it once we are done. Keeping the path 81 # to each generator that is used in sys.path is likely harmless and 82 # arguably a good idea. 83 path = os.path.abspath(path) 84 if path not in sys.path: 85 sys.path.insert(0, path) 86 else: 87 generator_name = 'gyp.generator.' + format 88 89 # These parameters are passed in order (as opposed to by key) 90 # because ActivePython cannot handle key parameters to __import__. 91 generator = __import__(generator_name, globals(), locals(), generator_name) 92 for (key, val) in generator.generator_default_variables.items(): 93 default_variables.setdefault(key, val) 94 95 # Give the generator the opportunity to set additional variables based on 96 # the params it will receive in the output phase. 97 if getattr(generator, 'CalculateVariables', None): 98 generator.CalculateVariables(default_variables, params) 99 100 # Give the generator the opportunity to set generator_input_info based on 101 # the params it will receive in the output phase. 102 if getattr(generator, 'CalculateGeneratorInputInfo', None): 103 generator.CalculateGeneratorInputInfo(params) 104 105 # Fetch the generator specific info that gets fed to input, we use getattr 106 # so we can default things and the generators only have to provide what 107 # they need. 108 generator_input_info = { 109 'non_configuration_keys': 110 getattr(generator, 'generator_additional_non_configuration_keys', []), 111 'path_sections': 112 getattr(generator, 'generator_additional_path_sections', []), 113 'extra_sources_for_rules': 114 getattr(generator, 'generator_extra_sources_for_rules', []), 115 'generator_supports_multiple_toolsets': 116 getattr(generator, 'generator_supports_multiple_toolsets', False), 117 'generator_wants_static_library_dependencies_adjusted': 118 getattr(generator, 119 'generator_wants_static_library_dependencies_adjusted', True), 120 'generator_wants_sorted_dependencies': 121 getattr(generator, 'generator_wants_sorted_dependencies', False), 122 'generator_filelist_paths': 123 getattr(generator, 'generator_filelist_paths', None), 124 } 125 126 # Process the input specific to this generator. 127 result = gyp.input.Load(build_files, default_variables, includes[:], 128 depth, generator_input_info, check, circular_check, 129 params['parallel'], params['root_targets']) 130 return [generator] + result 131 132def NameValueListToDict(name_value_list): 133 """ 134 Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary 135 of the pairs. If a string is simply NAME, then the value in the dictionary 136 is set to True. If VALUE can be converted to an integer, it is. 137 """ 138 result = { } 139 for item in name_value_list: 140 tokens = item.split('=', 1) 141 if len(tokens) == 2: 142 # If we can make it an int, use that, otherwise, use the string. 143 try: 144 token_value = int(tokens[1]) 145 except ValueError: 146 token_value = tokens[1] 147 # Set the variable to the supplied value. 148 result[tokens[0]] = token_value 149 else: 150 # No value supplied, treat it as a boolean and set it. 151 result[tokens[0]] = True 152 return result 153 154def ShlexEnv(env_name): 155 flags = os.environ.get(env_name, []) 156 if flags: 157 flags = shlex.split(flags) 158 return flags 159 160def FormatOpt(opt, value): 161 if opt.startswith('--'): 162 return '%s=%s' % (opt, value) 163 return opt + value 164 165def RegenerateAppendFlag(flag, values, predicate, env_name, options): 166 """Regenerate a list of command line flags, for an option of action='append'. 167 168 The |env_name|, if given, is checked in the environment and used to generate 169 an initial list of options, then the options that were specified on the 170 command line (given in |values|) are appended. This matches the handling of 171 environment variables and command line flags where command line flags override 172 the environment, while not requiring the environment to be set when the flags 173 are used again. 174 """ 175 flags = [] 176 if options.use_environment and env_name: 177 for flag_value in ShlexEnv(env_name): 178 value = FormatOpt(flag, predicate(flag_value)) 179 if value in flags: 180 flags.remove(value) 181 flags.append(value) 182 if values: 183 for flag_value in values: 184 flags.append(FormatOpt(flag, predicate(flag_value))) 185 return flags 186 187def RegenerateFlags(options): 188 """Given a parsed options object, and taking the environment variables into 189 account, returns a list of flags that should regenerate an equivalent options 190 object (even in the absence of the environment variables.) 191 192 Any path options will be normalized relative to depth. 193 194 The format flag is not included, as it is assumed the calling generator will 195 set that as appropriate. 196 """ 197 def FixPath(path): 198 path = gyp.common.FixIfRelativePath(path, options.depth) 199 if not path: 200 return os.path.curdir 201 return path 202 203 def Noop(value): 204 return value 205 206 # We always want to ignore the environment when regenerating, to avoid 207 # duplicate or changed flags in the environment at the time of regeneration. 208 flags = ['--ignore-environment'] 209 for name, metadata in options._regeneration_metadata.iteritems(): 210 opt = metadata['opt'] 211 value = getattr(options, name) 212 value_predicate = metadata['type'] == 'path' and FixPath or Noop 213 action = metadata['action'] 214 env_name = metadata['env_name'] 215 if action == 'append': 216 flags.extend(RegenerateAppendFlag(opt, value, value_predicate, 217 env_name, options)) 218 elif action in ('store', None): # None is a synonym for 'store'. 219 if value: 220 flags.append(FormatOpt(opt, value_predicate(value))) 221 elif options.use_environment and env_name and os.environ.get(env_name): 222 flags.append(FormatOpt(opt, value_predicate(os.environ.get(env_name)))) 223 elif action in ('store_true', 'store_false'): 224 if ((action == 'store_true' and value) or 225 (action == 'store_false' and not value)): 226 flags.append(opt) 227 elif options.use_environment and env_name: 228 print >>sys.stderr, ('Warning: environment regeneration unimplemented ' 229 'for %s flag %r env_name %r' % (action, opt, 230 env_name)) 231 else: 232 print >>sys.stderr, ('Warning: regeneration unimplemented for action %r ' 233 'flag %r' % (action, opt)) 234 235 return flags 236 237class RegeneratableOptionParser(optparse.OptionParser): 238 def __init__(self): 239 self.__regeneratable_options = {} 240 optparse.OptionParser.__init__(self) 241 242 def add_option(self, *args, **kw): 243 """Add an option to the parser. 244 245 This accepts the same arguments as OptionParser.add_option, plus the 246 following: 247 regenerate: can be set to False to prevent this option from being included 248 in regeneration. 249 env_name: name of environment variable that additional values for this 250 option come from. 251 type: adds type='path', to tell the regenerator that the values of 252 this option need to be made relative to options.depth 253 """ 254 env_name = kw.pop('env_name', None) 255 if 'dest' in kw and kw.pop('regenerate', True): 256 dest = kw['dest'] 257 258 # The path type is needed for regenerating, for optparse we can just treat 259 # it as a string. 260 type = kw.get('type') 261 if type == 'path': 262 kw['type'] = 'string' 263 264 self.__regeneratable_options[dest] = { 265 'action': kw.get('action'), 266 'type': type, 267 'env_name': env_name, 268 'opt': args[0], 269 } 270 271 optparse.OptionParser.add_option(self, *args, **kw) 272 273 def parse_args(self, *args): 274 values, args = optparse.OptionParser.parse_args(self, *args) 275 values._regeneration_metadata = self.__regeneratable_options 276 return values, args 277 278def gyp_main(args): 279 my_name = os.path.basename(sys.argv[0]) 280 281 parser = RegeneratableOptionParser() 282 usage = 'usage: %s [options ...] [build_file ...]' 283 parser.set_usage(usage.replace('%s', '%prog')) 284 parser.add_option('--build', dest='configs', action='append', 285 help='configuration for build after project generation') 286 parser.add_option('--check', dest='check', action='store_true', 287 help='check format of gyp files') 288 parser.add_option('--config-dir', dest='config_dir', action='store', 289 env_name='GYP_CONFIG_DIR', default=None, 290 help='The location for configuration files like ' 291 'include.gypi.') 292 parser.add_option('-d', '--debug', dest='debug', metavar='DEBUGMODE', 293 action='append', default=[], help='turn on a debugging ' 294 'mode for debugging GYP. Supported modes are "variables", ' 295 '"includes" and "general" or "all" for all of them.') 296 parser.add_option('-D', dest='defines', action='append', metavar='VAR=VAL', 297 env_name='GYP_DEFINES', 298 help='sets variable VAR to value VAL') 299 parser.add_option('--depth', dest='depth', metavar='PATH', type='path', 300 help='set DEPTH gyp variable to a relative path to PATH') 301 parser.add_option('-f', '--format', dest='formats', action='append', 302 env_name='GYP_GENERATORS', regenerate=False, 303 help='output formats to generate') 304 parser.add_option('-G', dest='generator_flags', action='append', default=[], 305 metavar='FLAG=VAL', env_name='GYP_GENERATOR_FLAGS', 306 help='sets generator flag FLAG to VAL') 307 parser.add_option('--generator-output', dest='generator_output', 308 action='store', default=None, metavar='DIR', type='path', 309 env_name='GYP_GENERATOR_OUTPUT', 310 help='puts generated build files under DIR') 311 parser.add_option('--ignore-environment', dest='use_environment', 312 action='store_false', default=True, regenerate=False, 313 help='do not read options from environment variables') 314 parser.add_option('-I', '--include', dest='includes', action='append', 315 metavar='INCLUDE', type='path', 316 help='files to include in all loaded .gyp files') 317 # --no-circular-check disables the check for circular relationships between 318 # .gyp files. These relationships should not exist, but they've only been 319 # observed to be harmful with the Xcode generator. Chromium's .gyp files 320 # currently have some circular relationships on non-Mac platforms, so this 321 # option allows the strict behavior to be used on Macs and the lenient 322 # behavior to be used elsewhere. 323 # TODO(mark): Remove this option when http://crbug.com/35878 is fixed. 324 parser.add_option('--no-circular-check', dest='circular_check', 325 action='store_false', default=True, regenerate=False, 326 help="don't check for circular relationships between files") 327 parser.add_option('--no-parallel', action='store_true', default=False, 328 help='Disable multiprocessing') 329 parser.add_option('-S', '--suffix', dest='suffix', default='', 330 help='suffix to add to generated files') 331 parser.add_option('--toplevel-dir', dest='toplevel_dir', action='store', 332 default=None, metavar='DIR', type='path', 333 help='directory to use as the root of the source tree') 334 parser.add_option('-R', '--root-target', dest='root_targets', 335 action='append', metavar='TARGET', 336 help='include only TARGET and its deep dependencies') 337 338 options, build_files_arg = parser.parse_args(args) 339 build_files = build_files_arg 340 341 # Set up the configuration directory (defaults to ~/.gyp) 342 if not options.config_dir: 343 home = None 344 home_dot_gyp = None 345 if options.use_environment: 346 home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None) 347 if home_dot_gyp: 348 home_dot_gyp = os.path.expanduser(home_dot_gyp) 349 350 if not home_dot_gyp: 351 home_vars = ['HOME'] 352 if sys.platform in ('cygwin', 'win32'): 353 home_vars.append('USERPROFILE') 354 for home_var in home_vars: 355 home = os.getenv(home_var) 356 if home != None: 357 home_dot_gyp = os.path.join(home, '.gyp') 358 if not os.path.exists(home_dot_gyp): 359 home_dot_gyp = None 360 else: 361 break 362 else: 363 home_dot_gyp = os.path.expanduser(options.config_dir) 364 365 if home_dot_gyp and not os.path.exists(home_dot_gyp): 366 home_dot_gyp = None 367 368 if not options.formats: 369 # If no format was given on the command line, then check the env variable. 370 generate_formats = [] 371 if options.use_environment: 372 generate_formats = os.environ.get('GYP_GENERATORS', []) 373 if generate_formats: 374 generate_formats = re.split('[\s,]', generate_formats) 375 if generate_formats: 376 options.formats = generate_formats 377 else: 378 # Nothing in the variable, default based on platform. 379 if sys.platform == 'darwin': 380 options.formats = ['xcode'] 381 elif sys.platform in ('win32', 'cygwin'): 382 options.formats = ['msvs'] 383 else: 384 options.formats = ['make'] 385 386 if not options.generator_output and options.use_environment: 387 g_o = os.environ.get('GYP_GENERATOR_OUTPUT') 388 if g_o: 389 options.generator_output = g_o 390 391 options.parallel = not options.no_parallel 392 393 for mode in options.debug: 394 gyp.debug[mode] = 1 395 396 # Do an extra check to avoid work when we're not debugging. 397 if DEBUG_GENERAL in gyp.debug: 398 DebugOutput(DEBUG_GENERAL, 'running with these options:') 399 for option, value in sorted(options.__dict__.items()): 400 if option[0] == '_': 401 continue 402 if isinstance(value, basestring): 403 DebugOutput(DEBUG_GENERAL, " %s: '%s'", option, value) 404 else: 405 DebugOutput(DEBUG_GENERAL, " %s: %s", option, value) 406 407 if not build_files: 408 build_files = FindBuildFiles() 409 if not build_files: 410 raise GypError((usage + '\n\n%s: error: no build_file') % 411 (my_name, my_name)) 412 413 # TODO(mark): Chromium-specific hack! 414 # For Chromium, the gyp "depth" variable should always be a relative path 415 # to Chromium's top-level "src" directory. If no depth variable was set 416 # on the command line, try to find a "src" directory by looking at the 417 # absolute path to each build file's directory. The first "src" component 418 # found will be treated as though it were the path used for --depth. 419 if not options.depth: 420 for build_file in build_files: 421 build_file_dir = os.path.abspath(os.path.dirname(build_file)) 422 build_file_dir_components = build_file_dir.split(os.path.sep) 423 components_len = len(build_file_dir_components) 424 for index in xrange(components_len - 1, -1, -1): 425 if build_file_dir_components[index] == 'src': 426 options.depth = os.path.sep.join(build_file_dir_components) 427 break 428 del build_file_dir_components[index] 429 430 # If the inner loop found something, break without advancing to another 431 # build file. 432 if options.depth: 433 break 434 435 if not options.depth: 436 raise GypError('Could not automatically locate src directory. This is' 437 'a temporary Chromium feature that will be removed. Use' 438 '--depth as a workaround.') 439 440 # If toplevel-dir is not set, we assume that depth is the root of our source 441 # tree. 442 if not options.toplevel_dir: 443 options.toplevel_dir = options.depth 444 445 # -D on the command line sets variable defaults - D isn't just for define, 446 # it's for default. Perhaps there should be a way to force (-F?) a 447 # variable's value so that it can't be overridden by anything else. 448 cmdline_default_variables = {} 449 defines = [] 450 if options.use_environment: 451 defines += ShlexEnv('GYP_DEFINES') 452 if options.defines: 453 defines += options.defines 454 cmdline_default_variables = NameValueListToDict(defines) 455 if DEBUG_GENERAL in gyp.debug: 456 DebugOutput(DEBUG_GENERAL, 457 "cmdline_default_variables: %s", cmdline_default_variables) 458 459 # Set up includes. 460 includes = [] 461 462 # If ~/.gyp/include.gypi exists, it'll be forcibly included into every 463 # .gyp file that's loaded, before anything else is included. 464 if home_dot_gyp != None: 465 default_include = os.path.join(home_dot_gyp, 'include.gypi') 466 if os.path.exists(default_include): 467 print 'Using overrides found in ' + default_include 468 includes.append(default_include) 469 470 # Command-line --include files come after the default include. 471 if options.includes: 472 includes.extend(options.includes) 473 474 # Generator flags should be prefixed with the target generator since they 475 # are global across all generator runs. 476 gen_flags = [] 477 if options.use_environment: 478 gen_flags += ShlexEnv('GYP_GENERATOR_FLAGS') 479 if options.generator_flags: 480 gen_flags += options.generator_flags 481 generator_flags = NameValueListToDict(gen_flags) 482 if DEBUG_GENERAL in gyp.debug.keys(): 483 DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags) 484 485 # Generate all requested formats (use a set in case we got one format request 486 # twice) 487 for format in set(options.formats): 488 params = {'options': options, 489 'build_files': build_files, 490 'generator_flags': generator_flags, 491 'cwd': os.getcwd(), 492 'build_files_arg': build_files_arg, 493 'gyp_binary': sys.argv[0], 494 'home_dot_gyp': home_dot_gyp, 495 'parallel': options.parallel, 496 'root_targets': options.root_targets} 497 498 # Start with the default variables from the command line. 499 [generator, flat_list, targets, data] = Load(build_files, format, 500 cmdline_default_variables, 501 includes, options.depth, 502 params, options.check, 503 options.circular_check) 504 505 # TODO(mark): Pass |data| for now because the generator needs a list of 506 # build files that came in. In the future, maybe it should just accept 507 # a list, and not the whole data dict. 508 # NOTE: flat_list is the flattened dependency graph specifying the order 509 # that targets may be built. Build systems that operate serially or that 510 # need to have dependencies defined before dependents reference them should 511 # generate targets in the order specified in flat_list. 512 generator.GenerateOutput(flat_list, targets, data, params) 513 514 if options.configs: 515 valid_configs = targets[flat_list[0]]['configurations'].keys() 516 for conf in options.configs: 517 if conf not in valid_configs: 518 raise GypError('Invalid config specified via --build: %s' % conf) 519 generator.PerformBuild(data, options.configs, params) 520 521 # Done 522 return 0 523 524 525def main(args): 526 try: 527 return gyp_main(args) 528 except GypError, e: 529 sys.stderr.write("gyp: %s\n" % e) 530 return 1 531 532# NOTE: setuptools generated console_scripts calls function with no arguments 533def script_main(): 534 return main(sys.argv[1:]) 535 536if __name__ == '__main__': 537 sys.exit(script_main()) 538