1#!/usr/bin/python 2 3# Copyright (c) 2009 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 gyp 8import gyp.common 9import gyp.SCons as SCons 10import os.path 11import pprint 12import re 13 14 15# TODO: remove when we delete the last WriteList() call in this module 16WriteList = SCons.WriteList 17 18 19generator_default_variables = { 20 'EXECUTABLE_PREFIX': '', 21 'EXECUTABLE_SUFFIX': '', 22 'STATIC_LIB_PREFIX': '${LIBPREFIX}', 23 'SHARED_LIB_PREFIX': '${SHLIBPREFIX}', 24 'STATIC_LIB_SUFFIX': '${LIBSUFFIX}', 25 'SHARED_LIB_SUFFIX': '${SHLIBSUFFIX}', 26 'INTERMEDIATE_DIR': '${INTERMEDIATE_DIR}', 27 'SHARED_INTERMEDIATE_DIR': '${SHARED_INTERMEDIATE_DIR}', 28 'OS': 'linux', 29 'PRODUCT_DIR': '$TOP_BUILDDIR', 30 'SHARED_LIB_DIR': '$LIB_DIR', 31 'LIB_DIR': '$LIB_DIR', 32 'RULE_INPUT_ROOT': '${SOURCE.filebase}', 33 'RULE_INPUT_EXT': '${SOURCE.suffix}', 34 'RULE_INPUT_NAME': '${SOURCE.file}', 35 'RULE_INPUT_PATH': '${SOURCE.abspath}', 36 'CONFIGURATION_NAME': '${CONFIG_NAME}', 37} 38 39# Tell GYP how to process the input for us. 40generator_handles_variants = True 41generator_wants_absolute_build_file_paths = True 42 43 44def FixPath(path, prefix): 45 if not os.path.isabs(path) and not path[0] == '$': 46 path = prefix + path 47 return path 48 49 50header = """\ 51# This file is generated; do not edit. 52""" 53 54 55_alias_template = """ 56if GetOption('verbose'): 57 _action = Action([%(action)s]) 58else: 59 _action = Action([%(action)s], %(message)s) 60_outputs = env.Alias( 61 ['_%(target_name)s_action'], 62 %(inputs)s, 63 _action 64) 65env.AlwaysBuild(_outputs) 66""" 67 68_run_as_template = """ 69if GetOption('verbose'): 70 _action = Action([%(action)s]) 71else: 72 _action = Action([%(action)s], %(message)s) 73""" 74 75_run_as_template_suffix = """ 76_run_as_target = env.Alias('run_%(target_name)s', target_files, _action) 77env.Requires(_run_as_target, [ 78 Alias('%(target_name)s'), 79]) 80env.AlwaysBuild(_run_as_target) 81""" 82 83_command_template = """ 84if GetOption('verbose'): 85 _action = Action([%(action)s]) 86else: 87 _action = Action([%(action)s], %(message)s) 88_outputs = env.Command( 89 %(outputs)s, 90 %(inputs)s, 91 _action 92) 93""" 94 95# This is copied from the default SCons action, updated to handle symlinks. 96_copy_action_template = """ 97import shutil 98import SCons.Action 99 100def _copy_files_or_dirs_or_symlinks(dest, src): 101 SCons.Node.FS.invalidate_node_memos(dest) 102 if SCons.Util.is_List(src) and os.path.isdir(dest): 103 for file in src: 104 shutil.copy2(file, dest) 105 return 0 106 elif os.path.islink(src): 107 linkto = os.readlink(src) 108 os.symlink(linkto, dest) 109 return 0 110 elif os.path.isfile(src): 111 return shutil.copy2(src, dest) 112 else: 113 return shutil.copytree(src, dest, 1) 114 115def _copy_files_or_dirs_or_symlinks_str(dest, src): 116 return 'Copying %s to %s ...' % (src, dest) 117 118GYPCopy = SCons.Action.ActionFactory(_copy_files_or_dirs_or_symlinks, 119 _copy_files_or_dirs_or_symlinks_str, 120 convert=str) 121""" 122 123_rule_template = """ 124%(name)s_additional_inputs = %(inputs)s 125%(name)s_outputs = %(outputs)s 126def %(name)s_emitter(target, source, env): 127 return (%(name)s_outputs, source + %(name)s_additional_inputs) 128if GetOption('verbose'): 129 %(name)s_action = Action([%(action)s]) 130else: 131 %(name)s_action = Action([%(action)s], %(message)s) 132env['BUILDERS']['%(name)s'] = Builder(action=%(name)s_action, 133 emitter=%(name)s_emitter) 134 135_outputs = [] 136_processed_input_files = [] 137for infile in input_files: 138 if (type(infile) == type('') 139 and not os.path.isabs(infile) 140 and not infile[0] == '$'): 141 infile = %(src_dir)r + infile 142 if str(infile).endswith('.%(extension)s'): 143 _generated = env.%(name)s(infile) 144 env.Precious(_generated) 145 _outputs.append(_generated) 146 %(process_outputs_as_sources_line)s 147 else: 148 _processed_input_files.append(infile) 149prerequisites.extend(_outputs) 150input_files = _processed_input_files 151""" 152 153_spawn_hack = """ 154import re 155import SCons.Platform.posix 156needs_shell = re.compile('["\\'><!^&]') 157def gyp_spawn(sh, escape, cmd, args, env): 158 def strip_scons_quotes(arg): 159 if arg[0] == '"' and arg[-1] == '"': 160 return arg[1:-1] 161 return arg 162 stripped_args = [strip_scons_quotes(a) for a in args] 163 if needs_shell.search(' '.join(stripped_args)): 164 return SCons.Platform.posix.exec_spawnvpe([sh, '-c', ' '.join(args)], env) 165 else: 166 return SCons.Platform.posix.exec_spawnvpe(stripped_args, env) 167""" 168 169 170def EscapeShellArgument(s): 171 """Quotes an argument so that it will be interpreted literally by a POSIX 172 shell. Taken from 173 http://stackoverflow.com/questions/35817/whats-the-best-way-to-escape-ossystem-calls-in-python 174 """ 175 return "'" + s.replace("'", "'\\''") + "'" 176 177 178def InvertNaiveSConsQuoting(s): 179 """SCons tries to "help" with quoting by naively putting double-quotes around 180 command-line arguments containing space or tab, which is broken for all 181 but trivial cases, so we undo it. (See quote_spaces() in Subst.py)""" 182 if ' ' in s or '\t' in s: 183 # Then SCons will put double-quotes around this, so add our own quotes 184 # to close its quotes at the beginning and end. 185 s = '"' + s + '"' 186 return s 187 188 189def EscapeSConsVariableExpansion(s): 190 """SCons has its own variable expansion syntax using $. We must escape it for 191 strings to be interpreted literally. For some reason this requires four 192 dollar signs, not two, even without the shell involved.""" 193 return s.replace('$', '$$$$') 194 195 196def EscapeCppDefine(s): 197 """Escapes a CPP define so that it will reach the compiler unaltered.""" 198 s = EscapeShellArgument(s) 199 s = InvertNaiveSConsQuoting(s) 200 s = EscapeSConsVariableExpansion(s) 201 return s 202 203 204def GenerateConfig(fp, config, indent='', src_dir=''): 205 """ 206 Generates SCons dictionary items for a gyp configuration. 207 208 This provides the main translation between the (lower-case) gyp settings 209 keywords and the (upper-case) SCons construction variables. 210 """ 211 var_mapping = { 212 'ASFLAGS' : 'asflags', 213 'CCFLAGS' : 'cflags', 214 'CFLAGS' : 'cflags_c', 215 'CXXFLAGS' : 'cflags_cc', 216 'CPPDEFINES' : 'defines', 217 'CPPPATH' : 'include_dirs', 218 # Add the ldflags value to $LINKFLAGS, but not $SHLINKFLAGS. 219 # SCons defines $SHLINKFLAGS to incorporate $LINKFLAGS, so 220 # listing both here would case 'ldflags' to get appended to 221 # both, and then have it show up twice on the command line. 222 'LINKFLAGS' : 'ldflags', 223 } 224 postamble='\n%s],\n' % indent 225 for scons_var in sorted(var_mapping.keys()): 226 gyp_var = var_mapping[scons_var] 227 value = config.get(gyp_var) 228 if value: 229 if gyp_var in ('defines',): 230 value = [EscapeCppDefine(v) for v in value] 231 if gyp_var in ('include_dirs',): 232 if src_dir and not src_dir.endswith('/'): 233 src_dir += '/' 234 result = [] 235 for v in value: 236 v = FixPath(v, src_dir) 237 # Force SCons to evaluate the CPPPATH directories at 238 # SConscript-read time, so delayed evaluation of $SRC_DIR 239 # doesn't point it to the --generator-output= directory. 240 result.append('env.Dir(%r)' % v) 241 value = result 242 else: 243 value = map(repr, value) 244 WriteList(fp, 245 value, 246 prefix=indent, 247 preamble='%s%s = [\n ' % (indent, scons_var), 248 postamble=postamble) 249 250 251def GenerateSConscript(output_filename, spec, build_file, build_file_data): 252 """ 253 Generates a SConscript file for a specific target. 254 255 This generates a SConscript file suitable for building any or all of 256 the target's configurations. 257 258 A SConscript file may be called multiple times to generate targets for 259 multiple configurations. Consequently, it needs to be ready to build 260 the target for any requested configuration, and therefore contains 261 information about the settings for all configurations (generated into 262 the SConscript file at gyp configuration time) as well as logic for 263 selecting (at SCons build time) the specific configuration being built. 264 265 The general outline of a generated SConscript file is: 266 267 -- Header 268 269 -- Import 'env'. This contains a $CONFIG_NAME construction 270 variable that specifies what configuration to build 271 (e.g. Debug, Release). 272 273 -- Configurations. This is a dictionary with settings for 274 the different configurations (Debug, Release) under which this 275 target can be built. The values in the dictionary are themselves 276 dictionaries specifying what construction variables should added 277 to the local copy of the imported construction environment 278 (Append), should be removed (FilterOut), and should outright 279 replace the imported values (Replace). 280 281 -- Clone the imported construction environment and update 282 with the proper configuration settings. 283 284 -- Initialize the lists of the targets' input files and prerequisites. 285 286 -- Target-specific actions and rules. These come after the 287 input file and prerequisite initializations because the 288 outputs of the actions and rules may affect the input file 289 list (process_outputs_as_sources) and get added to the list of 290 prerequisites (so that they're guaranteed to be executed before 291 building the target). 292 293 -- Call the Builder for the target itself. 294 295 -- Arrange for any copies to be made into installation directories. 296 297 -- Set up the {name} Alias (phony Node) for the target as the 298 primary handle for building all of the target's pieces. 299 300 -- Use env.Require() to make sure the prerequisites (explicitly 301 specified, but also including the actions and rules) are built 302 before the target itself. 303 304 -- Return the {name} Alias to the calling SConstruct file 305 so it can be added to the list of default targets. 306 """ 307 scons_target = SCons.Target(spec) 308 309 gyp_dir = os.path.dirname(output_filename) 310 if not gyp_dir: 311 gyp_dir = '.' 312 gyp_dir = os.path.abspath(gyp_dir) 313 314 output_dir = os.path.dirname(output_filename) 315 src_dir = build_file_data['_DEPTH'] 316 src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) 317 subdir = gyp.common.RelativePath(os.path.dirname(build_file), src_dir) 318 src_subdir = '$SRC_DIR/' + subdir 319 src_subdir_ = src_subdir + '/' 320 321 component_name = os.path.splitext(os.path.basename(build_file))[0] 322 target_name = spec['target_name'] 323 324 if not os.path.exists(gyp_dir): 325 os.makedirs(gyp_dir) 326 fp = open(output_filename, 'w') 327 fp.write(header) 328 329 fp.write('\nimport os\n') 330 fp.write('\nImport("env")\n') 331 332 # 333 fp.write('\n') 334 fp.write('env = env.Clone(COMPONENT_NAME=%s,\n' % repr(component_name)) 335 fp.write(' TARGET_NAME=%s)\n' % repr(target_name)) 336 337 # 338 for config in spec['configurations'].itervalues(): 339 if config.get('scons_line_length'): 340 fp.write(_spawn_hack) 341 break 342 343 # 344 indent = ' ' * 12 345 fp.write('\n') 346 fp.write('configurations = {\n') 347 for config_name, config in spec['configurations'].iteritems(): 348 fp.write(' \'%s\' : {\n' % config_name) 349 350 fp.write(' \'Append\' : dict(\n') 351 GenerateConfig(fp, config, indent, src_subdir) 352 libraries = spec.get('libraries') 353 if libraries: 354 WriteList(fp, 355 map(repr, libraries), 356 prefix=indent, 357 preamble='%sLIBS = [\n ' % indent, 358 postamble='\n%s],\n' % indent) 359 fp.write(' ),\n') 360 361 fp.write(' \'FilterOut\' : dict(\n' ) 362 for key, var in config.get('scons_remove', {}).iteritems(): 363 fp.write(' %s = %s,\n' % (key, repr(var))) 364 fp.write(' ),\n') 365 366 fp.write(' \'Replace\' : dict(\n' ) 367 scons_settings = config.get('scons_variable_settings', {}) 368 for key in sorted(scons_settings.keys()): 369 val = pprint.pformat(scons_settings[key]) 370 fp.write(' %s = %s,\n' % (key, val)) 371 if 'c++' in spec.get('link_languages', []): 372 fp.write(' %s = %s,\n' % ('LINK', repr('$CXX'))) 373 if config.get('scons_line_length'): 374 fp.write(' SPAWN = gyp_spawn,\n') 375 fp.write(' ),\n') 376 377 fp.write(' \'ImportExternal\' : [\n' ) 378 for var in config.get('scons_import_variables', []): 379 fp.write(' %s,\n' % repr(var)) 380 fp.write(' ],\n') 381 382 fp.write(' \'PropagateExternal\' : [\n' ) 383 for var in config.get('scons_propagate_variables', []): 384 fp.write(' %s,\n' % repr(var)) 385 fp.write(' ],\n') 386 387 fp.write(' },\n') 388 fp.write('}\n') 389 390 fp.write('\n' 391 'config = configurations[env[\'CONFIG_NAME\']]\n' 392 'env.Append(**config[\'Append\'])\n' 393 'env.FilterOut(**config[\'FilterOut\'])\n' 394 'env.Replace(**config[\'Replace\'])\n') 395 396 fp.write('\n' 397 '# Scons forces -fPIC for SHCCFLAGS on some platforms.\n' 398 '# Disable that so we can control it from cflags in gyp.\n' 399 '# Note that Scons itself is inconsistent with its -fPIC\n' 400 '# setting. SHCCFLAGS forces -fPIC, and SHCFLAGS does not.\n' 401 '# This will make SHCCFLAGS consistent with SHCFLAGS.\n' 402 'env[\'SHCCFLAGS\'] = [\'$CCFLAGS\']\n') 403 404 fp.write('\n' 405 'for _var in config[\'ImportExternal\']:\n' 406 ' if _var in ARGUMENTS:\n' 407 ' env[_var] = ARGUMENTS[_var]\n' 408 ' elif _var in os.environ:\n' 409 ' env[_var] = os.environ[_var]\n' 410 'for _var in config[\'PropagateExternal\']:\n' 411 ' if _var in ARGUMENTS:\n' 412 ' env[_var] = ARGUMENTS[_var]\n' 413 ' elif _var in os.environ:\n' 414 ' env[\'ENV\'][_var] = os.environ[_var]\n') 415 416 fp.write('\n' 417 "env['ENV']['LD_LIBRARY_PATH'] = env.subst('$LIB_DIR')\n") 418 419 # 420 #fp.write("\nif env.has_key('CPPPATH'):\n") 421 #fp.write(" env['CPPPATH'] = map(env.Dir, env['CPPPATH'])\n") 422 423 variants = spec.get('variants', {}) 424 for setting in sorted(variants.keys()): 425 if_fmt = 'if ARGUMENTS.get(%s) not in (None, \'0\'):\n' 426 fp.write('\n') 427 fp.write(if_fmt % repr(setting.upper())) 428 fp.write(' env.AppendUnique(\n') 429 GenerateConfig(fp, variants[setting], indent, src_subdir) 430 fp.write(' )\n') 431 432 # 433 scons_target.write_input_files(fp) 434 435 fp.write('\n') 436 fp.write('target_files = []\n') 437 prerequisites = spec.get('scons_prerequisites', []) 438 fp.write('prerequisites = %s\n' % pprint.pformat(prerequisites)) 439 440 actions = spec.get('actions', []) 441 for action in actions: 442 a = ['cd', src_subdir, '&&'] + action['action'] 443 message = action.get('message') 444 if message: 445 message = repr(message) 446 inputs = [FixPath(f, src_subdir_) for f in action.get('inputs', [])] 447 outputs = [FixPath(f, src_subdir_) for f in action.get('outputs', [])] 448 if outputs: 449 template = _command_template 450 else: 451 template = _alias_template 452 fp.write(template % { 453 'inputs' : pprint.pformat(inputs), 454 'outputs' : pprint.pformat(outputs), 455 'action' : pprint.pformat(a), 456 'message' : message, 457 'target_name': target_name, 458 }) 459 if int(action.get('process_outputs_as_sources', 0)): 460 fp.write('input_files.extend(_outputs)\n') 461 fp.write('prerequisites.extend(_outputs)\n') 462 fp.write('target_files.extend(_outputs)\n') 463 464 rules = spec.get('rules', []) 465 for rule in rules: 466 name = rule['rule_name'] 467 a = ['cd', src_subdir, '&&'] + rule['action'] 468 message = rule.get('message') 469 if message: 470 message = repr(message) 471 if int(rule.get('process_outputs_as_sources', 0)): 472 poas_line = '_processed_input_files.extend(_generated)' 473 else: 474 poas_line = '_processed_input_files.append(infile)' 475 inputs = [FixPath(f, src_subdir_) for f in rule.get('inputs', [])] 476 outputs = [FixPath(f, src_subdir_) for f in rule.get('outputs', [])] 477 fp.write(_rule_template % { 478 'inputs' : pprint.pformat(inputs), 479 'outputs' : pprint.pformat(outputs), 480 'action' : pprint.pformat(a), 481 'extension' : rule['extension'], 482 'name' : name, 483 'message' : message, 484 'process_outputs_as_sources_line' : poas_line, 485 'src_dir' : src_subdir_, 486 }) 487 488 scons_target.write_target(fp, src_subdir) 489 490 copies = spec.get('copies', []) 491 if copies: 492 fp.write(_copy_action_template) 493 for copy in copies: 494 destdir = None 495 files = None 496 try: 497 destdir = copy['destination'] 498 except KeyError, e: 499 gyp.common.ExceptionAppend( 500 e, 501 "Required 'destination' key missing for 'copies' in %s." % build_file) 502 raise 503 try: 504 files = copy['files'] 505 except KeyError, e: 506 gyp.common.ExceptionAppend( 507 e, "Required 'files' key missing for 'copies' in %s." % build_file) 508 raise 509 if not files: 510 # TODO: should probably add a (suppressible) warning; 511 # a null file list may be unintentional. 512 continue 513 if not destdir: 514 raise Exception( 515 "Required 'destination' key is empty for 'copies' in %s." % build_file) 516 517 fmt = ('\n' 518 '_outputs = env.Command(%s,\n' 519 ' %s,\n' 520 ' GYPCopy(\'$TARGET\', \'$SOURCE\'))\n') 521 for f in copy['files']: 522 # Remove trailing separators so basename() acts like Unix basename and 523 # always returns the last element, whether a file or dir. Without this, 524 # only the contents, not the directory itself, are copied (and nothing 525 # might be copied if dest already exists, since scons thinks nothing needs 526 # to be done). 527 dest = os.path.join(destdir, os.path.basename(f.rstrip(os.sep))) 528 f = FixPath(f, src_subdir_) 529 dest = FixPath(dest, src_subdir_) 530 fp.write(fmt % (repr(dest), repr(f))) 531 fp.write('target_files.extend(_outputs)\n') 532 533 run_as = spec.get('run_as') 534 if run_as: 535 action = run_as.get('action', []) 536 working_directory = run_as.get('working_directory') 537 if not working_directory: 538 working_directory = gyp_dir 539 else: 540 if not os.path.isabs(working_directory): 541 working_directory = os.path.normpath(os.path.join(gyp_dir, 542 working_directory)) 543 if run_as.get('environment'): 544 for (key, val) in run_as.get('environment').iteritems(): 545 action = ['%s="%s"' % (key, val)] + action 546 action = ['cd', '"%s"' % working_directory, '&&'] + action 547 fp.write(_run_as_template % { 548 'action' : pprint.pformat(action), 549 'message' : run_as.get('message', ''), 550 }) 551 552 fmt = "\ngyp_target = env.Alias('%s', target_files)\n" 553 fp.write(fmt % target_name) 554 555 dependencies = spec.get('scons_dependencies', []) 556 if dependencies: 557 WriteList(fp, dependencies, preamble='dependencies = [\n ', 558 postamble='\n]\n') 559 fp.write('env.Requires(target_files, dependencies)\n') 560 fp.write('env.Requires(gyp_target, dependencies)\n') 561 fp.write('for prerequisite in prerequisites:\n') 562 fp.write(' env.Requires(prerequisite, dependencies)\n') 563 fp.write('env.Requires(gyp_target, prerequisites)\n') 564 565 if run_as: 566 fp.write(_run_as_template_suffix % { 567 'target_name': target_name, 568 }) 569 570 fp.write('Return("gyp_target")\n') 571 572 fp.close() 573 574 575############################################################################# 576# TEMPLATE BEGIN 577 578_wrapper_template = """\ 579 580__doc__ = ''' 581Wrapper configuration for building this entire "solution," 582including all the specific targets in various *.scons files. 583''' 584 585import os 586import sys 587 588import SCons.Environment 589import SCons.Util 590 591def GetProcessorCount(): 592 ''' 593 Detects the number of CPUs on the system. Adapted form: 594 http://codeliberates.blogspot.com/2008/05/detecting-cpuscores-in-python.html 595 ''' 596 # Linux, Unix and Mac OS X: 597 if hasattr(os, 'sysconf'): 598 if os.sysconf_names.has_key('SC_NPROCESSORS_ONLN'): 599 # Linux and Unix or Mac OS X with python >= 2.5: 600 return os.sysconf('SC_NPROCESSORS_ONLN') 601 else: # Mac OS X with Python < 2.5: 602 return int(os.popen2("sysctl -n hw.ncpu")[1].read()) 603 # Windows: 604 if os.environ.has_key('NUMBER_OF_PROCESSORS'): 605 return max(int(os.environ.get('NUMBER_OF_PROCESSORS', '1')), 1) 606 return 1 # Default 607 608# Support PROGRESS= to show progress in different ways. 609p = ARGUMENTS.get('PROGRESS') 610if p == 'spinner': 611 Progress(['/\\r', '|\\r', '\\\\\\r', '-\\r'], 612 interval=5, 613 file=open('/dev/tty', 'w')) 614elif p == 'name': 615 Progress('$TARGET\\r', overwrite=True, file=open('/dev/tty', 'w')) 616 617# Set the default -j value based on the number of processors. 618SetOption('num_jobs', GetProcessorCount() + 1) 619 620# Have SCons use its cached dependency information. 621SetOption('implicit_cache', 1) 622 623# Only re-calculate MD5 checksums if a timestamp has changed. 624Decider('MD5-timestamp') 625 626# Since we set the -j value by default, suppress SCons warnings about being 627# unable to support parallel build on versions of Python with no threading. 628default_warnings = ['no-no-parallel-support'] 629SetOption('warn', default_warnings + GetOption('warn')) 630 631AddOption('--mode', nargs=1, dest='conf_list', default=[], 632 action='append', help='Configuration to build.') 633 634AddOption('--verbose', dest='verbose', default=False, 635 action='store_true', help='Verbose command-line output.') 636 637 638# 639sconscript_file_map = %(sconscript_files)s 640 641class LoadTarget: 642 ''' 643 Class for deciding if a given target sconscript is to be included 644 based on a list of included target names, optionally prefixed with '-' 645 to exclude a target name. 646 ''' 647 def __init__(self, load): 648 ''' 649 Initialize a class with a list of names for possible loading. 650 651 Arguments: 652 load: list of elements in the LOAD= specification 653 ''' 654 self.included = set([c for c in load if not c.startswith('-')]) 655 self.excluded = set([c[1:] for c in load if c.startswith('-')]) 656 657 if not self.included: 658 self.included = set(['all']) 659 660 def __call__(self, target): 661 ''' 662 Returns True if the specified target's sconscript file should be 663 loaded, based on the initialized included and excluded lists. 664 ''' 665 return (target in self.included or 666 ('all' in self.included and not target in self.excluded)) 667 668if 'LOAD' in ARGUMENTS: 669 load = ARGUMENTS['LOAD'].split(',') 670else: 671 load = [] 672load_target = LoadTarget(load) 673 674sconscript_files = [] 675for target, sconscript in sconscript_file_map.iteritems(): 676 if load_target(target): 677 sconscript_files.append(sconscript) 678 679 680target_alias_list= [] 681 682conf_list = GetOption('conf_list') 683if conf_list: 684 # In case the same --mode= value was specified multiple times. 685 conf_list = list(set(conf_list)) 686else: 687 conf_list = [%(default_configuration)r] 688 689sconsbuild_dir = Dir(%(sconsbuild_dir)s) 690 691 692def FilterOut(self, **kw): 693 kw = SCons.Environment.copy_non_reserved_keywords(kw) 694 for key, val in kw.items(): 695 envval = self.get(key, None) 696 if envval is None: 697 # No existing variable in the environment, so nothing to delete. 698 continue 699 700 for vremove in val: 701 # Use while not if, so we can handle duplicates. 702 while vremove in envval: 703 envval.remove(vremove) 704 705 self[key] = envval 706 707 # TODO(sgk): SCons.Environment.Append() has much more logic to deal 708 # with various types of values. We should handle all those cases in here 709 # too. (If variable is a dict, etc.) 710 711 712non_compilable_suffixes = { 713 'LINUX' : set([ 714 '.bdic', 715 '.css', 716 '.dat', 717 '.fragment', 718 '.gperf', 719 '.h', 720 '.hh', 721 '.hpp', 722 '.html', 723 '.hxx', 724 '.idl', 725 '.in', 726 '.in0', 727 '.in1', 728 '.js', 729 '.mk', 730 '.rc', 731 '.sigs', 732 '', 733 ]), 734 'WINDOWS' : set([ 735 '.h', 736 '.hh', 737 '.hpp', 738 '.dat', 739 '.idl', 740 '.in', 741 '.in0', 742 '.in1', 743 ]), 744} 745 746def compilable(env, file): 747 base, ext = os.path.splitext(str(file)) 748 if ext in non_compilable_suffixes[env['TARGET_PLATFORM']]: 749 return False 750 return True 751 752def compilable_files(env, sources): 753 return [x for x in sources if compilable(env, x)] 754 755def GypProgram(env, target, source, *args, **kw): 756 source = compilable_files(env, source) 757 result = env.Program(target, source, *args, **kw) 758 if env.get('INCREMENTAL'): 759 env.Precious(result) 760 return result 761 762def GypTestProgram(env, target, source, *args, **kw): 763 source = compilable_files(env, source) 764 result = env.Program(target, source, *args, **kw) 765 if env.get('INCREMENTAL'): 766 env.Precious(*result) 767 return result 768 769def GypLibrary(env, target, source, *args, **kw): 770 source = compilable_files(env, source) 771 result = env.Library(target, source, *args, **kw) 772 return result 773 774def GypLoadableModule(env, target, source, *args, **kw): 775 source = compilable_files(env, source) 776 result = env.LoadableModule(target, source, *args, **kw) 777 return result 778 779def GypStaticLibrary(env, target, source, *args, **kw): 780 source = compilable_files(env, source) 781 result = env.StaticLibrary(target, source, *args, **kw) 782 return result 783 784def GypSharedLibrary(env, target, source, *args, **kw): 785 source = compilable_files(env, source) 786 result = env.SharedLibrary(target, source, *args, **kw) 787 if env.get('INCREMENTAL'): 788 env.Precious(result) 789 return result 790 791def add_gyp_methods(env): 792 env.AddMethod(GypProgram) 793 env.AddMethod(GypTestProgram) 794 env.AddMethod(GypLibrary) 795 env.AddMethod(GypLoadableModule) 796 env.AddMethod(GypStaticLibrary) 797 env.AddMethod(GypSharedLibrary) 798 799 env.AddMethod(FilterOut) 800 801 env.AddMethod(compilable) 802 803 804base_env = Environment( 805 tools = %(scons_tools)s, 806 INTERMEDIATE_DIR='$OBJ_DIR/${COMPONENT_NAME}/_${TARGET_NAME}_intermediate', 807 LIB_DIR='$TOP_BUILDDIR/lib', 808 OBJ_DIR='$TOP_BUILDDIR/obj', 809 SCONSBUILD_DIR=sconsbuild_dir.abspath, 810 SHARED_INTERMEDIATE_DIR='$OBJ_DIR/_global_intermediate', 811 SRC_DIR=Dir(%(src_dir)r), 812 TARGET_PLATFORM='LINUX', 813 TOP_BUILDDIR='$SCONSBUILD_DIR/$CONFIG_NAME', 814 LIBPATH=['$LIB_DIR'], 815) 816 817if not GetOption('verbose'): 818 base_env.SetDefault( 819 ARCOMSTR='Creating library $TARGET', 820 ASCOMSTR='Assembling $TARGET', 821 CCCOMSTR='Compiling $TARGET', 822 CONCATSOURCECOMSTR='ConcatSource $TARGET', 823 CXXCOMSTR='Compiling $TARGET', 824 LDMODULECOMSTR='Building loadable module $TARGET', 825 LINKCOMSTR='Linking $TARGET', 826 MANIFESTCOMSTR='Updating manifest for $TARGET', 827 MIDLCOMSTR='Compiling IDL $TARGET', 828 PCHCOMSTR='Precompiling $TARGET', 829 RANLIBCOMSTR='Indexing $TARGET', 830 RCCOMSTR='Compiling resource $TARGET', 831 SHCCCOMSTR='Compiling $TARGET', 832 SHCXXCOMSTR='Compiling $TARGET', 833 SHLINKCOMSTR='Linking $TARGET', 834 SHMANIFESTCOMSTR='Updating manifest for $TARGET', 835 ) 836 837add_gyp_methods(base_env) 838 839for conf in conf_list: 840 env = base_env.Clone(CONFIG_NAME=conf) 841 SConsignFile(env.File('$TOP_BUILDDIR/.sconsign').abspath) 842 for sconscript in sconscript_files: 843 target_alias = env.SConscript(sconscript, exports=['env']) 844 if target_alias: 845 target_alias_list.extend(target_alias) 846 847Default(Alias('all', target_alias_list)) 848 849help_fmt = ''' 850Usage: hammer [SCONS_OPTIONS] [VARIABLES] [TARGET] ... 851 852Local command-line build options: 853 --mode=CONFIG Configuration to build: 854 --mode=Debug [default] 855 --mode=Release 856 --verbose Print actual executed command lines. 857 858Supported command-line build variables: 859 LOAD=[module,...] Comma-separated list of components to load in the 860 dependency graph ('-' prefix excludes) 861 PROGRESS=type Display a progress indicator: 862 name: print each evaluated target name 863 spinner: print a spinner every 5 targets 864 865The following TARGET names can also be used as LOAD= module names: 866 867%%s 868''' 869 870if GetOption('help'): 871 def columnar_text(items, width=78, indent=2, sep=2): 872 result = [] 873 colwidth = max(map(len, items)) + sep 874 cols = (width - indent) / colwidth 875 if cols < 1: 876 cols = 1 877 rows = (len(items) + cols - 1) / cols 878 indent = '%%*s' %% (indent, '') 879 sep = indent 880 for row in xrange(0, rows): 881 result.append(sep) 882 for i in xrange(row, len(items), rows): 883 result.append('%%-*s' %% (colwidth, items[i])) 884 sep = '\\n' + indent 885 result.append('\\n') 886 return ''.join(result) 887 888 load_list = set(sconscript_file_map.keys()) 889 target_aliases = set(map(str, target_alias_list)) 890 891 common = load_list and target_aliases 892 load_only = load_list - common 893 target_only = target_aliases - common 894 help_text = [help_fmt %% columnar_text(sorted(list(common)))] 895 if target_only: 896 fmt = "The following are additional TARGET names:\\n\\n%%s\\n" 897 help_text.append(fmt %% columnar_text(sorted(list(target_only)))) 898 if load_only: 899 fmt = "The following are additional LOAD= module names:\\n\\n%%s\\n" 900 help_text.append(fmt %% columnar_text(sorted(list(load_only)))) 901 Help(''.join(help_text)) 902""" 903 904# TEMPLATE END 905############################################################################# 906 907 908def GenerateSConscriptWrapper(build_file, build_file_data, name, 909 output_filename, sconscript_files, 910 default_configuration): 911 """ 912 Generates the "wrapper" SConscript file (analogous to the Visual Studio 913 solution) that calls all the individual target SConscript files. 914 """ 915 output_dir = os.path.dirname(output_filename) 916 src_dir = build_file_data['_DEPTH'] 917 src_dir_rel = gyp.common.RelativePath(src_dir, output_dir) 918 if not src_dir_rel: 919 src_dir_rel = '.' 920 scons_settings = build_file_data.get('scons_settings', {}) 921 sconsbuild_dir = scons_settings.get('sconsbuild_dir', '#') 922 scons_tools = scons_settings.get('tools', ['default']) 923 924 sconscript_file_lines = ['dict('] 925 for target in sorted(sconscript_files.keys()): 926 sconscript = sconscript_files[target] 927 sconscript_file_lines.append(' %s = %r,' % (target, sconscript)) 928 sconscript_file_lines.append(')') 929 930 fp = open(output_filename, 'w') 931 fp.write(header) 932 fp.write(_wrapper_template % { 933 'default_configuration' : default_configuration, 934 'name' : name, 935 'scons_tools' : repr(scons_tools), 936 'sconsbuild_dir' : repr(sconsbuild_dir), 937 'sconscript_files' : '\n'.join(sconscript_file_lines), 938 'src_dir' : src_dir_rel, 939 }) 940 fp.close() 941 942 # Generate the SConstruct file that invokes the wrapper SConscript. 943 dir, fname = os.path.split(output_filename) 944 SConstruct = os.path.join(dir, 'SConstruct') 945 fp = open(SConstruct, 'w') 946 fp.write(header) 947 fp.write('SConscript(%s)\n' % repr(fname)) 948 fp.close() 949 950 951def TargetFilename(target, build_file=None, output_suffix=''): 952 """Returns the .scons file name for the specified target. 953 """ 954 if build_file is None: 955 build_file, target = gyp.common.ParseQualifiedTarget(target)[:2] 956 output_file = os.path.join(os.path.dirname(build_file), 957 target + output_suffix + '.scons') 958 return output_file 959 960 961def GenerateOutput(target_list, target_dicts, data, params): 962 """ 963 Generates all the output files for the specified targets. 964 """ 965 options = params['options'] 966 967 if options.generator_output: 968 def output_path(filename): 969 return filename.replace(params['cwd'], options.generator_output) 970 else: 971 def output_path(filename): 972 return filename 973 974 default_configuration = None 975 976 for qualified_target in target_list: 977 spec = target_dicts[qualified_target] 978 if spec['toolset'] != 'target': 979 raise Exception( 980 'Multiple toolsets not supported in scons build (target %s)' % 981 qualified_target) 982 scons_target = SCons.Target(spec) 983 if scons_target.is_ignored: 984 continue 985 986 # TODO: assumes the default_configuration of the first target 987 # non-Default target is the correct default for all targets. 988 # Need a better model for handle variation between targets. 989 if (not default_configuration and 990 spec['default_configuration'] != 'Default'): 991 default_configuration = spec['default_configuration'] 992 993 build_file, target = gyp.common.ParseQualifiedTarget(qualified_target)[:2] 994 output_file = TargetFilename(target, build_file, options.suffix) 995 if options.generator_output: 996 output_file = output_path(output_file) 997 998 if not spec.has_key('libraries'): 999 spec['libraries'] = [] 1000 1001 # Add dependent static library targets to the 'libraries' value. 1002 deps = spec.get('dependencies', []) 1003 spec['scons_dependencies'] = [] 1004 for d in deps: 1005 td = target_dicts[d] 1006 target_name = td['target_name'] 1007 spec['scons_dependencies'].append("Alias('%s')" % target_name) 1008 if td['type'] in ('static_library', 'shared_library'): 1009 libname = td.get('product_name', target_name) 1010 spec['libraries'].append('lib' + libname) 1011 if td['type'] == 'loadable_module': 1012 prereqs = spec.get('scons_prerequisites', []) 1013 # TODO: parameterize with <(SHARED_LIBRARY_*) variables? 1014 td_target = SCons.Target(td) 1015 td_target.target_prefix = '${SHLIBPREFIX}' 1016 td_target.target_suffix = '${SHLIBSUFFIX}' 1017 1018 GenerateSConscript(output_file, spec, build_file, data[build_file]) 1019 1020 if not default_configuration: 1021 default_configuration = 'Default' 1022 1023 for build_file in sorted(data.keys()): 1024 path, ext = os.path.splitext(build_file) 1025 if ext != '.gyp': 1026 continue 1027 output_dir, basename = os.path.split(path) 1028 output_filename = path + '_main' + options.suffix + '.scons' 1029 1030 all_targets = gyp.common.AllTargets(target_list, target_dicts, build_file) 1031 sconscript_files = {} 1032 for t in all_targets: 1033 scons_target = SCons.Target(target_dicts[t]) 1034 if scons_target.is_ignored: 1035 continue 1036 bf, target = gyp.common.ParseQualifiedTarget(t)[:2] 1037 target_filename = TargetFilename(target, bf, options.suffix) 1038 tpath = gyp.common.RelativePath(target_filename, output_dir) 1039 sconscript_files[target] = tpath 1040 1041 output_filename = output_path(output_filename) 1042 if sconscript_files: 1043 GenerateSConscriptWrapper(build_file, data[build_file], basename, 1044 output_filename, sconscript_files, 1045 default_configuration) 1046