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