pch.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. # Alexander Afanasyev (UCLA), 2014
  4. """
  5. Enable precompiled C++ header support (currently only clang++ and g++ are supported)
  6. To use this tool, wscript should look like:
  7. def options(opt):
  8. opt.load('pch')
  9. # This will add `--with-pch` configure option.
  10. # Unless --with-pch during configure stage specified, the precompiled header support is disabled
  11. def configure(conf):
  12. conf.load('pch')
  13. # this will set conf.env.WITH_PCH if --with-pch is specified and the supported compiler is used
  14. # Unless conf.env.WITH_PCH is set, the precompiled header support is disabled
  15. def build(bld):
  16. bld(features='cxx pch',
  17. target='precompiled-headers',
  18. name='precompiled-headers',
  19. headers='a.h b.h c.h', # headers to pre-compile into `precompiled-headers`
  20. # Other parameters to compile precompiled headers
  21. # includes=...,
  22. # export_includes=...,
  23. # use=...,
  24. # ...
  25. # Exported parameters will be propagated even if precompiled headers are disabled
  26. )
  27. bld(
  28. target='test',
  29. features='cxx cxxprogram',
  30. source='a.cpp b.cpp d.cpp main.cpp',
  31. use='precompiled-headers',
  32. )
  33. # or
  34. bld(
  35. target='test',
  36. features='pch cxx cxxprogram',
  37. source='a.cpp b.cpp d.cpp main.cpp',
  38. headers='a.h b.h c.h',
  39. )
  40. Note that precompiled header must have multiple inclusion guards. If the guards are missing, any benefit of precompiled header will be voided and compilation may fail in some cases.
  41. """
  42. import os
  43. from waflib import Task, TaskGen, Utils
  44. from waflib.Tools import c_preproc, cxx
  45. PCH_COMPILER_OPTIONS = {
  46. 'clang++': [['-include'], '.pch', ['-x', 'c++-header']],
  47. 'g++': [['-include'], '.gch', ['-x', 'c++-header']],
  48. }
  49. def options(opt):
  50. opt.add_option('--without-pch', action='store_false', default=True, dest='with_pch', help='''Try to use precompiled header to speed up compilation (only g++ and clang++)''')
  51. def configure(conf):
  52. if (conf.options.with_pch and conf.env['COMPILER_CXX'] in PCH_COMPILER_OPTIONS.keys()):
  53. conf.env.WITH_PCH = True
  54. flags = PCH_COMPILER_OPTIONS[conf.env['COMPILER_CXX']]
  55. conf.env.CXXPCH_F = flags[0]
  56. conf.env.CXXPCH_EXT = flags[1]
  57. conf.env.CXXPCH_FLAGS = flags[2]
  58. @TaskGen.feature('pch')
  59. @TaskGen.before('process_source')
  60. def apply_pch(self):
  61. if not self.env.WITH_PCH:
  62. return
  63. if getattr(self.bld, 'pch_tasks', None) is None:
  64. self.bld.pch_tasks = {}
  65. if getattr(self, 'headers', None) is None:
  66. return
  67. self.headers = self.to_nodes(self.headers)
  68. if getattr(self, 'name', None):
  69. try:
  70. task = self.bld.pch_tasks["%s.%s" % (self.name, self.idx)]
  71. self.bld.fatal("Duplicated 'pch' task with name %r" % "%s.%s" % (self.name, self.idx))
  72. except KeyError:
  73. pass
  74. out = '%s.%d%s' % (self.target, self.idx, self.env['CXXPCH_EXT'])
  75. out = self.path.find_or_declare(out)
  76. task = self.create_task('gchx', self.headers, out)
  77. # target should be an absolute path of `out`, but without precompiled header extension
  78. task.target = out.abspath()[:-len(out.suffix())]
  79. self.pch_task = task
  80. if getattr(self, 'name', None):
  81. self.bld.pch_tasks["%s.%s" % (self.name, self.idx)] = task
  82. @TaskGen.feature('cxx')
  83. @TaskGen.after_method('process_source', 'propagate_uselib_vars')
  84. def add_pch(self):
  85. if not (self.env['WITH_PCH'] and getattr(self, 'use', None) and getattr(self, 'compiled_tasks', None) and getattr(self.bld, 'pch_tasks', None)):
  86. return
  87. pch = None
  88. # find pch task, if any
  89. if getattr(self, 'pch_task', None):
  90. pch = self.pch_task
  91. else:
  92. for use in Utils.to_list(self.use):
  93. try:
  94. pch = self.bld.pch_tasks[use]
  95. except KeyError:
  96. pass
  97. if pch:
  98. for x in self.compiled_tasks:
  99. x.env.append_value('CXXFLAGS', self.env['CXXPCH_F'] + [pch.target])
  100. class gchx(Task.Task):
  101. run_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${CXXPCH_FLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${CXXPCH_F:SRC} ${CXX_SRC_F}${SRC[0].abspath()} ${CXX_TGT_F}${TGT[0].abspath()} ${CPPFLAGS}'
  102. scan = c_preproc.scan
  103. color = 'BLUE'
  104. ext_out=['.h']
  105. def runnable_status(self):
  106. try:
  107. node_deps = self.generator.bld.node_deps[self.uid()]
  108. except KeyError:
  109. node_deps = []
  110. ret = Task.Task.runnable_status(self)
  111. if ret == Task.SKIP_ME and self.env.CXX_NAME == 'clang':
  112. t = os.stat(self.outputs[0].abspath()).st_mtime
  113. for n in self.inputs + node_deps:
  114. if os.stat(n.abspath()).st_mtime > t:
  115. return Task.RUN_ME
  116. return ret