3 # Alexander Afanasyev (UCLA), 2014
6 Enable precompiled C++ header support (currently only clang++ and g++ are supported)
8 To use this tool, wscript should look like:
12 # This will add `--with-pch` configure option.
13 # Unless --with-pch during configure stage specified, the precompiled header support is disabled
17 # this will set conf.env.WITH_PCH if --with-pch is specified and the supported compiler is used
18 # Unless conf.env.WITH_PCH is set, the precompiled header support is disabled
21 bld(features='cxx pch',
22 target='precompiled-headers',
23 name='precompiled-headers',
24 headers='a.h b.h c.h', # headers to pre-compile into `precompiled-headers`
26 # Other parameters to compile precompiled headers
28 # export_includes=...,
32 # Exported parameters will be propagated even if precompiled headers are disabled
37 features='cxx cxxprogram',
38 source='a.cpp b.cpp d.cpp main.cpp',
39 use='precompiled-headers',
46 features='pch cxx cxxprogram',
47 source='a.cpp b.cpp d.cpp main.cpp',
48 headers='a.h b.h c.h',
51 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.
55 from waflib import Task, TaskGen, Utils
56 from waflib.Tools import c_preproc, cxx
59 PCH_COMPILER_OPTIONS = {
60 'clang++': [['-include'], '.pch', ['-x', 'c++-header']],
61 'g++': [['-include'], '.gch', ['-x', 'c++-header']],
66 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++)''')
69 if (conf.options.with_pch and conf.env['COMPILER_CXX'] in PCH_COMPILER_OPTIONS.keys()):
70 conf.env.WITH_PCH = True
71 flags = PCH_COMPILER_OPTIONS[conf.env['COMPILER_CXX']]
72 conf.env.CXXPCH_F = flags[0]
73 conf.env.CXXPCH_EXT = flags[1]
74 conf.env.CXXPCH_FLAGS = flags[2]
77 @TaskGen.feature('pch')
78 @TaskGen.before('process_source')
80 if not self.env.WITH_PCH:
83 if getattr(self.bld, 'pch_tasks', None) is None:
84 self.bld.pch_tasks = {}
86 if getattr(self, 'headers', None) is None:
89 self.headers = self.to_nodes(self.headers)
91 if getattr(self, 'name', None):
93 task = self.bld.pch_tasks[self.name]
94 self.bld.fatal("Duplicated 'pch' task with name %r" % "%s.%s" % (self.name, self.idx))
98 out = '%s.%d%s' % (self.target, self.idx, self.env['CXXPCH_EXT'])
99 out = self.path.find_or_declare(out)
100 task = self.create_task('gchx', self.headers, out)
102 # target should be an absolute path of `out`, but without precompiled header extension
103 task.target = out.abspath()[:-len(out.suffix())]
106 if getattr(self, 'name', None):
107 self.bld.pch_tasks[self.name] = task
109 @TaskGen.feature('cxx')
110 @TaskGen.after_method('process_source', 'propagate_uselib_vars')
112 if not (self.env['WITH_PCH'] and getattr(self, 'use', None) and getattr(self, 'compiled_tasks', None) and getattr(self.bld, 'pch_tasks', None)):
116 # find pch task, if any
118 if getattr(self, 'pch_task', None):
121 for use in Utils.to_list(self.use):
123 pch = self.bld.pch_tasks[use]
128 for x in self.compiled_tasks:
129 x.env.append_value('CXXFLAGS', self.env['CXXPCH_F'] + [pch.target])
131 class gchx(Task.Task):
132 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}'
133 scan = c_preproc.scan
137 def runnable_status(self):
139 node_deps = self.generator.bld.node_deps[self.uid()]
142 ret = Task.Task.runnable_status(self)
143 if ret == Task.SKIP_ME and self.env.CXX_NAME == 'clang':
144 t = os.stat(self.outputs[0].abspath()).st_mtime
145 for n in self.inputs + node_deps:
146 if os.stat(n.abspath()).st_mtime > t: