blob: 103e752838c674e0733f742077b2353125086d07 [file] [log] [blame]
Alexander Afanasyev8552a382014-05-15 20:13:42 -07001#! /usr/bin/env python
2# encoding: utf-8
Alexander Afanasyev58ea4582014-05-30 08:38:56 +03003# Alexander Afanasyev (UCLA), 2014
Alexander Afanasyev8552a382014-05-15 20:13:42 -07004
Alexander Afanasyev58ea4582014-05-30 08:38:56 +03005"""
6Enable precompiled C++ header support (currently only clang++ and g++ are supported)
7
8To use this tool, wscript should look like:
9
10 def options(opt):
11 opt.load('pch')
12 # This will add `--with-pch` configure option.
13 # Unless --with-pch during configure stage specified, the precompiled header support is disabled
14
15 def configure(conf):
16 conf.load('pch')
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
19
20 def build(bld):
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`
25
26 # Other parameters to compile precompiled headers
27 # includes=...,
28 # export_includes=...,
29 # use=...,
30 # ...
31
32 # Exported parameters will be propagated even if precompiled headers are disabled
33 )
34
35 bld(
36 target='test',
37 features='cxx cxxprogram',
38 source='a.cpp b.cpp d.cpp main.cpp',
39 use='precompiled-headers',
40 )
41
42 # or
43
44 bld(
45 target='test',
46 features='pch cxx cxxprogram',
47 source='a.cpp b.cpp d.cpp main.cpp',
48 headers='a.h b.h c.h',
49 )
50
51Note 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.
52"""
53
54import os
Davide Pesavento0064c1d2018-03-03 18:43:53 -050055from waflib import Task, TaskGen, Utils
Alexander Afanasyev8552a382014-05-15 20:13:42 -070056from waflib.Tools import c_preproc, cxx
57
Alexander Afanasyev8552a382014-05-15 20:13:42 -070058
59PCH_COMPILER_OPTIONS = {
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030060 'clang++': [['-include'], '.pch', ['-x', 'c++-header']],
61 'g++': [['-include'], '.gch', ['-x', 'c++-header']],
Alexander Afanasyev8552a382014-05-15 20:13:42 -070062}
63
Alexander Afanasyev8552a382014-05-15 20:13:42 -070064
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030065def options(opt):
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++)''')
Alexander Afanasyev8552a382014-05-15 20:13:42 -070067
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030068def configure(conf):
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]
Alexander Afanasyev8552a382014-05-15 20:13:42 -070075
Alexander Afanasyev8552a382014-05-15 20:13:42 -070076
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030077@TaskGen.feature('pch')
78@TaskGen.before('process_source')
79def apply_pch(self):
80 if not self.env.WITH_PCH:
81 return
Alexander Afanasyev8552a382014-05-15 20:13:42 -070082
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030083 if getattr(self.bld, 'pch_tasks', None) is None:
84 self.bld.pch_tasks = {}
Alexander Afanasyev8552a382014-05-15 20:13:42 -070085
Davide Pesaventodc26b272014-06-27 16:41:51 +020086 if getattr(self, 'headers', None) is None:
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030087 return
Alexander Afanasyev8552a382014-05-15 20:13:42 -070088
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030089 self.headers = self.to_nodes(self.headers)
Alexander Afanasyev8552a382014-05-15 20:13:42 -070090
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030091 if getattr(self, 'name', None):
92 try:
Davide Pesavento0064c1d2018-03-03 18:43:53 -050093 task = self.bld.pch_tasks["%s.%s" % (self.name, self.idx)]
94 self.bld.fatal("Duplicated 'pch' task with name %r" % "%s.%s" % (self.name, self.idx))
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030095 except KeyError:
96 pass
Alexander Afanasyev8552a382014-05-15 20:13:42 -070097
Alexander Afanasyev58ea4582014-05-30 08:38:56 +030098 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)
Alexander Afanasyev8552a382014-05-15 20:13:42 -0700101
Alexander Afanasyev58ea4582014-05-30 08:38:56 +0300102 # target should be an absolute path of `out`, but without precompiled header extension
103 task.target = out.abspath()[:-len(out.suffix())]
104
Davide Pesaventodc26b272014-06-27 16:41:51 +0200105 self.pch_task = task
Alexander Afanasyev58ea4582014-05-30 08:38:56 +0300106 if getattr(self, 'name', None):
Davide Pesavento0064c1d2018-03-03 18:43:53 -0500107 self.bld.pch_tasks["%s.%s" % (self.name, self.idx)] = task
Alexander Afanasyev58ea4582014-05-30 08:38:56 +0300108
109@TaskGen.feature('cxx')
110@TaskGen.after_method('process_source', 'propagate_uselib_vars')
111def add_pch(self):
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)):
113 return
114
115 pch = None
116 # find pch task, if any
117
118 if getattr(self, 'pch_task', None):
119 pch = self.pch_task
120 else:
121 for use in Utils.to_list(self.use):
122 try:
123 pch = self.bld.pch_tasks[use]
124 except KeyError:
125 pass
126
127 if pch:
128 for x in self.compiled_tasks:
129 x.env.append_value('CXXFLAGS', self.env['CXXPCH_F'] + [pch.target])
130
131class gchx(Task.Task):
Davide Pesavento0064c1d2018-03-03 18:43:53 -0500132 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}'
Alexander Afanasyev58ea4582014-05-30 08:38:56 +0300133 scan = c_preproc.scan
134 color = 'BLUE'
135 ext_out=['.h']
136
137 def runnable_status(self):
138 try:
139 node_deps = self.generator.bld.node_deps[self.uid()]
140 except KeyError:
141 node_deps = []
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:
147 return Task.RUN_ME
148 return ret