batched_cc.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2006-2015 (ita)
  4. """
  5. Instead of compiling object files one by one, c/c++ compilers are often able to compile at once:
  6. cc -c ../file1.c ../file2.c ../file3.c
  7. Files are output on the directory where the compiler is called, and dependencies are more difficult
  8. to track (do not run the command on all source files if only one file changes)
  9. As such, we do as if the files were compiled one by one, but no command is actually run:
  10. replace each cc/cpp Task by a TaskSlave. A new task called TaskMaster collects the
  11. signatures from each slave and finds out the command-line to run.
  12. Just import this module to start using it:
  13. def build(bld):
  14. bld.load('batched_cc')
  15. Note that this is provided as an example, unity builds are recommended
  16. for best performance results (fewer tasks and fewer jobs to execute).
  17. See waflib/extras/unity.py.
  18. """
  19. from waflib import Task, Utils
  20. from waflib.TaskGen import extension, feature, after_method
  21. from waflib.Tools import c, cxx
  22. MAX_BATCH = 50
  23. c_str = '${CC} ${ARCH_ST:ARCH} ${CFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${tsk.batch_incpaths()} ${DEFINES_ST:DEFINES} -c ${SRCLST} ${CXX_TGT_F_BATCHED} ${CPPFLAGS}'
  24. c_fun, _ = Task.compile_fun_noshell(c_str)
  25. cxx_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${tsk.batch_incpaths()} ${DEFINES_ST:DEFINES} -c ${SRCLST} ${CXX_TGT_F_BATCHED} ${CPPFLAGS}'
  26. cxx_fun, _ = Task.compile_fun_noshell(cxx_str)
  27. count = 70000
  28. class batch(Task.Task):
  29. color = 'PINK'
  30. after = ['c', 'cxx']
  31. before = ['cprogram', 'cshlib', 'cstlib', 'cxxprogram', 'cxxshlib', 'cxxstlib']
  32. def uid(self):
  33. return Utils.h_list([Task.Task.uid(self), self.generator.idx, self.generator.path.abspath(), self.generator.target])
  34. def __str__(self):
  35. return 'Batch compilation for %d slaves' % len(self.slaves)
  36. def __init__(self, *k, **kw):
  37. Task.Task.__init__(self, *k, **kw)
  38. self.slaves = []
  39. self.inputs = []
  40. self.hasrun = 0
  41. global count
  42. count += 1
  43. self.idx = count
  44. def add_slave(self, slave):
  45. self.slaves.append(slave)
  46. self.set_run_after(slave)
  47. def runnable_status(self):
  48. for t in self.run_after:
  49. if not t.hasrun:
  50. return Task.ASK_LATER
  51. for t in self.slaves:
  52. #if t.executed:
  53. if t.hasrun != Task.SKIPPED:
  54. return Task.RUN_ME
  55. return Task.SKIP_ME
  56. def get_cwd(self):
  57. return self.slaves[0].outputs[0].parent
  58. def batch_incpaths(self):
  59. st = self.env.CPPPATH_ST
  60. return [st % node.abspath() for node in self.generator.includes_nodes]
  61. def run(self):
  62. self.outputs = []
  63. srclst = []
  64. slaves = []
  65. for t in self.slaves:
  66. if t.hasrun != Task.SKIPPED:
  67. slaves.append(t)
  68. srclst.append(t.inputs[0].abspath())
  69. self.env.SRCLST = srclst
  70. if self.slaves[0].__class__.__name__ == 'c':
  71. ret = c_fun(self)
  72. else:
  73. ret = cxx_fun(self)
  74. if ret:
  75. return ret
  76. for t in slaves:
  77. t.old_post_run()
  78. def hook(cls_type):
  79. def n_hook(self, node):
  80. ext = '.obj' if self.env.CC_NAME == 'msvc' else '.o'
  81. name = node.name
  82. k = name.rfind('.')
  83. if k >= 0:
  84. basename = name[:k] + ext
  85. else:
  86. basename = name + ext
  87. outdir = node.parent.get_bld().make_node('%d' % self.idx)
  88. outdir.mkdir()
  89. out = outdir.find_or_declare(basename)
  90. task = self.create_task(cls_type, node, out)
  91. try:
  92. self.compiled_tasks.append(task)
  93. except AttributeError:
  94. self.compiled_tasks = [task]
  95. if not getattr(self, 'masters', None):
  96. self.masters = {}
  97. self.allmasters = []
  98. def fix_path(tsk):
  99. if self.env.CC_NAME == 'msvc':
  100. tsk.env.append_unique('CXX_TGT_F_BATCHED', '/Fo%s\\' % outdir.abspath())
  101. if not node.parent in self.masters:
  102. m = self.masters[node.parent] = self.master = self.create_task('batch')
  103. fix_path(m)
  104. self.allmasters.append(m)
  105. else:
  106. m = self.masters[node.parent]
  107. if len(m.slaves) > MAX_BATCH:
  108. m = self.masters[node.parent] = self.master = self.create_task('batch')
  109. fix_path(m)
  110. self.allmasters.append(m)
  111. m.add_slave(task)
  112. return task
  113. return n_hook
  114. extension('.c')(hook('c'))
  115. extension('.cpp','.cc','.cxx','.C','.c++')(hook('cxx'))
  116. @feature('cprogram', 'cshlib', 'cstaticlib', 'cxxprogram', 'cxxshlib', 'cxxstlib')
  117. @after_method('apply_link')
  118. def link_after_masters(self):
  119. if getattr(self, 'allmasters', None):
  120. for m in self.allmasters:
  121. self.link_task.set_run_after(m)
  122. # Modify the c and cxx task classes - in theory it would be best to
  123. # create subclasses and to re-map the c/c++ extensions
  124. for x in ('c', 'cxx'):
  125. t = Task.classes[x]
  126. def run(self):
  127. pass
  128. def post_run(self):
  129. pass
  130. setattr(t, 'oldrun', getattr(t, 'run', None))
  131. setattr(t, 'run', run)
  132. setattr(t, 'old_post_run', t.post_run)
  133. setattr(t, 'post_run', post_run)