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