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, duplicate_basename_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 duplicate_basename_check, 130 params['parallel'], params['root_targets']) 131 return [generator] + result 132 133def NameValueListToDict(name_value_list): 134 """ 135 Takes an array of strings of the form 'NAME=VALUE' and creates a dictionary 136 of the pairs. If a string is simply NAME, then the value in the dictionary 137 is set to True. If VALUE can be converted to an integer, it is. 138 """ 139 result = { } 140 for item in name_value_list: 141 tokens = item.split('=', 1) 142 if len(tokens) == 2: 143 # If we can make it an int, use that, otherwise, use the string. 144 try: 145 token_value = int(tokens[1]) 146 except ValueError: 147 token_value = tokens[1] 148 # Set the variable to the supplied value. 149 result[tokens[0]] = token_value 150 else: 151 # No value supplied, treat it as a boolean and set it. 152 result[tokens[0]] = True 153 return result 154 155def ShlexEnv(env_name): 156 flags = os.environ.get(env_name, []) 157 if flags: 158 flags = shlex.split(flags) 159 return flags 160 161def FormatOpt(opt, value): 162 if opt.startswith('--'): 163 return '%s=%s' % (opt, value) 164 return opt + value 165 166def RegenerateAppendFlag(flag, values, predicate, env_name, options): 167 """Regenerate a list of command line flags, for an option of action='append'. 168 169 The |env_name|, if given, is checked in the environment and used to generate 170 an initial list of options, then the options that were specified on the 171 command line (given in |values|) are appended. This matches the handling of 172 environment variables and command line flags where command line flags override 173 the environment, while not requiring the environment to be set when the flags 174 are used again. 175 """ 176 flags = [] 177 if options.use_environment and env_name: 178 for flag_value in ShlexEnv(env_name): 179 value = FormatOpt(flag, predicate(flag_value)) 180 if value in flags: 181 flags.remove(value) 182 flags.append(value) 183 if values: 184 for flag_value in values: 185 flags.append(FormatOpt(flag, predicate(flag_value))) 186 return flags 187 188def RegenerateFlags(options): 189 """Given a parsed options object, and taking the environment variables into 190 account, returns a list of flags that should regenerate an equivalent options 191 object (even in the absence of the environment variables.) 192 193 Any path options will be normalized relative to depth. 194 195 The format flag is not included, as it is assumed the calling generator will 196 set that as appropriate. 197 """ 198 def FixPath(path): 199 path = gyp.common.FixIfRelativePath(path, options.depth) 200 if not path: 201 return os.path.curdir 202 return path 203 204 def Noop(value): 205 return value 206 207 # We always want to ignore the environment when regenerating, to avoid 208 # duplicate or changed flags in the environment at the time of regeneration. 209 flags = ['--ignore-environment'] 210 for name, metadata in options._regeneration_metadata.iteritems(): 211 opt = metadata['opt'] 212 value = getattr(options, name) 213 value_predicate = metadata['type'] == 'path' and FixPath or Noop 214 action = metadata['action'] 215 env_name = metadata['env_name'] 216 if action == 'append': 217 flags.extend(RegenerateAppendFlag(opt, value, value_predicate, 218 env_name, options)) 219 elif action in ('store', None): # None is a synonym for 'store'. 220 if value: 221 flags.append(FormatOpt(opt, value_predicate(value))) 222 elif options.use_environment and env_name and os.environ.get(env_name): 223 flags.append(FormatOpt(opt, value_predicate(os.environ.get(env_name)))) 224 elif action in ('store_true', 'store_false'): 225 if ((action == 'store_true' and value) or 226 (action == 'store_false' and not value)): 227 flags.append(opt) 228 elif options.use_environment and env_name: 229 print >>sys.stderr, ('Warning: environment regeneration unimplemented ' 230 'for %s flag %r env_name %r' % (action, opt, 231 env_name)) 232 else: 233 print >>sys.stderr, ('Warning: regeneration unimplemented for action %r ' 234 'flag %r' % (action, opt)) 235 236 return flags 237 238class RegeneratableOptionParser(optparse.OptionParser): 239 def __init__(self): 240 self.__regeneratable_options = {} 241 optparse.OptionParser.__init__(self) 242 243 def add_option(self, *args, **kw): 244 """Add an option to the parser. 245 246 This accepts the same arguments as OptionParser.add_option, plus the 247 following: 248 regenerate: can be set to False to prevent this option from being included 249 in regeneration. 250 env_name: name of environment variable that additional values for this 251 option come from. 252 type: adds type='path', to tell the regenerator that the values of 253 this option need to be made relative to options.depth 254 """ 255 env_name = kw.pop('env_name', None) 256 if 'dest' in kw and kw.pop('regenerate', True): 257 dest = kw['dest'] 258 259 # The path type is needed for regenerating, for optparse we can just treat 260 # it as a string. 261 type = kw.get('type') 262 if type == 'path': 263 kw['type'] = 'string' 264 265 self.__regeneratable_options[dest] = { 266 'action': kw.get('action'), 267 'type': type, 268 'env_name': env_name, 269 'opt': args[0], 270 } 271 272 optparse.OptionParser.add_option(self, *args, **kw) 273 274 def parse_args(self, *args): 275 values, args = optparse.OptionParser.parse_args(self, *args) 276 values._regeneration_metadata = self.__regeneratable_options 277 return values, args 278 279def gyp_main(args): 280 my_name = os.path.basename(sys.argv[0]) 281 282 parser = RegeneratableOptionParser() 283 usage = 'usage: %s [options ...] [build_file ...]' 284 parser.set_usage(usage.replace('%s', '%prog')) 285 parser.add_option('--build', dest='configs', action='append', 286 help='configuration for build after project generation') 287 parser.add_option('--check', dest='check', action='store_true', 288 help='check format of gyp files') 289 parser.add_option('--config-dir', dest='config_dir', action='store', 290 env_name='GYP_CONFIG_DIR', default=None, 291 help='The location for configuration files like ' 292 'include.gypi.') 293 parser.add_option('-d', '--debug', dest='debug', metavar='DEBUGMODE', 294 action='append', default=[], help='turn on a debugging ' 295 'mode for debugging GYP. Supported modes are "variables", ' 296 '"includes" and "general" or "all" for all of them.') 297 parser.add_option('-D', dest='defines', action='append', metavar='VAR=VAL', 298 env_name='GYP_DEFINES', 299 help='sets variable VAR to value VAL') 300 parser.add_option('--depth', dest='depth', metavar='PATH', type='path', 301 help='set DEPTH gyp variable to a relative path to PATH') 302 parser.add_option('-f', '--format', dest='formats', action='append', 303 env_name='GYP_GENERATORS', regenerate=False, 304 help='output formats to generate') 305 parser.add_option('-G', dest='generator_flags', action='append', default=[], 306 metavar='FLAG=VAL', env_name='GYP_GENERATOR_FLAGS', 307 help='sets generator flag FLAG to VAL') 308 parser.add_option('--generator-output', dest='generator_output', 309 action='store', default=None, metavar='DIR', type='path', 310 env_name='GYP_GENERATOR_OUTPUT', 311 help='puts generated build files under DIR') 312 parser.add_option('--ignore-environment', dest='use_environment', 313 action='store_false', default=True, regenerate=False, 314 help='do not read options from environment variables') 315 parser.add_option('-I', '--include', dest='includes', action='append', 316 metavar='INCLUDE', type='path', 317 help='files to include in all loaded .gyp files') 318 # --no-circular-check disables the check for circular relationships between 319 # .gyp files. These relationships should not exist, but they've only been 320 # observed to be harmful with the Xcode generator. Chromium's .gyp files 321 # currently have some circular relationships on non-Mac platforms, so this 322 # option allows the strict behavior to be used on Macs and the lenient 323 # behavior to be used elsewhere. 324 # TODO(mark): Remove this option when http://crbug.com/35878 is fixed. 325 parser.add_option('--no-circular-check', dest='circular_check', 326 action='store_false', default=True, regenerate=False, 327 help="don't check for circular relationships between files") 328 # --no-duplicate-basename-check disables the check for duplicate basenames 329 # in a static_library/shared_library project. Visual C++ 2008 generator 330 # doesn't support this configuration. Libtool on Mac also generates warnings 331 # when duplicate basenames are passed into Make generator on Mac. 332 # TODO(yukawa): Remove this option when these legacy generators are 333 # deprecated. 334 parser.add_option('--no-duplicate-basename-check', 335 dest='duplicate_basename_check', action='store_false', 336 default=True, regenerate=False, 337 help="don't check for duplicate basenames") 338 parser.add_option('--no-parallel', action='store_true', default=False, 339 help='Disable multiprocessing') 340 parser.add_option('-S', '--suffix', dest='suffix', default='', 341 help='suffix to add to generated files') 342 parser.add_option('--toplevel-dir', dest='toplevel_dir', action='store', 343 default=None, metavar='DIR', type='path', 344 help='directory to use as the root of the source tree') 345 parser.add_option('-R', '--root-target', dest='root_targets', 346 action='append', metavar='TARGET', 347 help='include only TARGET and its deep dependencies') 348 349 options, build_files_arg = parser.parse_args(args) 350 build_files = build_files_arg 351 352 # Set up the configuration directory (defaults to ~/.gyp) 353 if not options.config_dir: 354 home = None 355 home_dot_gyp = None 356 if options.use_environment: 357 home_dot_gyp = os.environ.get('GYP_CONFIG_DIR', None) 358 if home_dot_gyp: 359 home_dot_gyp = os.path.expanduser(home_dot_gyp) 360 361 if not home_dot_gyp: 362 home_vars = ['HOME'] 363 if sys.platform in ('cygwin', 'win32'): 364 home_vars.append('USERPROFILE') 365 for home_var in home_vars: 366 home = os.getenv(home_var) 367 if home != None: 368 home_dot_gyp = os.path.join(home, '.gyp') 369 if not os.path.exists(home_dot_gyp): 370 home_dot_gyp = None 371 else: 372 break 373 else: 374 home_dot_gyp = os.path.expanduser(options.config_dir) 375 376 if home_dot_gyp and not os.path.exists(home_dot_gyp): 377 home_dot_gyp = None 378 379 if not options.formats: 380 # If no format was given on the command line, then check the env variable. 381 generate_formats = [] 382 if options.use_environment: 383 generate_formats = os.environ.get('GYP_GENERATORS', []) 384 if generate_formats: 385 generate_formats = re.split('[\s,]', generate_formats) 386 if generate_formats: 387 options.formats = generate_formats 388 else: 389 # Nothing in the variable, default based on platform. 390 if sys.platform == 'darwin': 391 options.formats = ['xcode'] 392 elif sys.platform in ('win32', 'cygwin'): 393 options.formats = ['msvs'] 394 else: 395 options.formats = ['make'] 396 397 if not options.generator_output and options.use_environment: 398 g_o = os.environ.get('GYP_GENERATOR_OUTPUT') 399 if g_o: 400 options.generator_output = g_o 401 402 options.parallel = not options.no_parallel 403 404 for mode in options.debug: 405 gyp.debug[mode] = 1 406 407 # Do an extra check to avoid work when we're not debugging. 408 if DEBUG_GENERAL in gyp.debug: 409 DebugOutput(DEBUG_GENERAL, 'running with these options:') 410 for option, value in sorted(options.__dict__.items()): 411 if option[0] == '_': 412 continue 413 if isinstance(value, basestring): 414 DebugOutput(DEBUG_GENERAL, " %s: '%s'", option, value) 415 else: 416 DebugOutput(DEBUG_GENERAL, " %s: %s", option, value) 417 418 if not build_files: 419 build_files = FindBuildFiles() 420 if not build_files: 421 raise GypError((usage + '\n\n%s: error: no build_file') % 422 (my_name, my_name)) 423 424 # TODO(mark): Chromium-specific hack! 425 # For Chromium, the gyp "depth" variable should always be a relative path 426 # to Chromium's top-level "src" directory. If no depth variable was set 427 # on the command line, try to find a "src" directory by looking at the 428 # absolute path to each build file's directory. The first "src" component 429 # found will be treated as though it were the path used for --depth. 430 if not options.depth: 431 for build_file in build_files: 432 build_file_dir = os.path.abspath(os.path.dirname(build_file)) 433 build_file_dir_components = build_file_dir.split(os.path.sep) 434 components_len = len(build_file_dir_components) 435 for index in xrange(components_len - 1, -1, -1): 436 if build_file_dir_components[index] == 'src': 437 options.depth = os.path.sep.join(build_file_dir_components) 438 break 439 del build_file_dir_components[index] 440 441 # If the inner loop found something, break without advancing to another 442 # build file. 443 if options.depth: 444 break 445 446 if not options.depth: 447 raise GypError('Could not automatically locate src directory. This is' 448 'a temporary Chromium feature that will be removed. Use' 449 '--depth as a workaround.') 450 451 # If toplevel-dir is not set, we assume that depth is the root of our source 452 # tree. 453 if not options.toplevel_dir: 454 options.toplevel_dir = options.depth 455 456 # -D on the command line sets variable defaults - D isn't just for define, 457 # it's for default. Perhaps there should be a way to force (-F?) a 458 # variable's value so that it can't be overridden by anything else. 459 cmdline_default_variables = {} 460 defines = [] 461 if options.use_environment: 462 defines += ShlexEnv('GYP_DEFINES') 463 if options.defines: 464 defines += options.defines 465 cmdline_default_variables = NameValueListToDict(defines) 466 if DEBUG_GENERAL in gyp.debug: 467 DebugOutput(DEBUG_GENERAL, 468 "cmdline_default_variables: %s", cmdline_default_variables) 469 470 # Set up includes. 471 includes = [] 472 473 # If ~/.gyp/include.gypi exists, it'll be forcibly included into every 474 # .gyp file that's loaded, before anything else is included. 475 if home_dot_gyp != None: 476 default_include = os.path.join(home_dot_gyp, 'include.gypi') 477 if os.path.exists(default_include): 478 print 'Using overrides found in ' + default_include 479 includes.append(default_include) 480 481 # Command-line --include files come after the default include. 482 if options.includes: 483 includes.extend(options.includes) 484 485 # Generator flags should be prefixed with the target generator since they 486 # are global across all generator runs. 487 gen_flags = [] 488 if options.use_environment: 489 gen_flags += ShlexEnv('GYP_GENERATOR_FLAGS') 490 if options.generator_flags: 491 gen_flags += options.generator_flags 492 generator_flags = NameValueListToDict(gen_flags) 493 if DEBUG_GENERAL in gyp.debug.keys(): 494 DebugOutput(DEBUG_GENERAL, "generator_flags: %s", generator_flags) 495 496 # Generate all requested formats (use a set in case we got one format request 497 # twice) 498 for format in set(options.formats): 499 params = {'options': options, 500 'build_files': build_files, 501 'generator_flags': generator_flags, 502 'cwd': os.getcwd(), 503 'build_files_arg': build_files_arg, 504 'gyp_binary': sys.argv[0], 505 'home_dot_gyp': home_dot_gyp, 506 'parallel': options.parallel, 507 'root_targets': options.root_targets} 508 509 # Start with the default variables from the command line. 510 [generator, flat_list, targets, data] = Load( 511 build_files, format, cmdline_default_variables, includes, options.depth, 512 params, options.check, options.circular_check, 513 options.duplicate_basename_check) 514 515 # TODO(mark): Pass |data| for now because the generator needs a list of 516 # build files that came in. In the future, maybe it should just accept 517 # a list, and not the whole data dict. 518 # NOTE: flat_list is the flattened dependency graph specifying the order 519 # that targets may be built. Build systems that operate serially or that 520 # need to have dependencies defined before dependents reference them should 521 # generate targets in the order specified in flat_list. 522 generator.GenerateOutput(flat_list, targets, data, params) 523 524 if options.configs: 525 valid_configs = targets[flat_list[0]]['configurations'].keys() 526 for conf in options.configs: 527 if conf not in valid_configs: 528 raise GypError('Invalid config specified via --build: %s' % conf) 529 generator.PerformBuild(data, options.configs, params) 530 531 # Done 532 return 0 533 534 535def main(args): 536 try: 537 return gyp_main(args) 538 except GypError, e: 539 sys.stderr.write("gyp: %s\n" % e) 540 return 1 541 542# NOTE: setuptools generated console_scripts calls function with no arguments 543def script_main(): 544 return main(sys.argv[1:]) 545 546if __name__ == '__main__': 547 sys.exit(script_main()) 548