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