PageRenderTime 27ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/CocosBuilder/libs/nodejs/lib/node_modules/npm/node_modules/node-gyp/gyp/pylib/gyp/generator/scons.py

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