blob: 087d633cc8f0c9579eb3532dce7b2def36d8ab12 [file] [log] [blame]
Alexander Afanasyev1160baa2014-04-10 18:50:29 -07001#! /usr/bin/env python
2# encoding: utf-8
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -07003# Alexander Afanasyev (UCLA), 2014
Alexander Afanasyev1160baa2014-04-10 18:50:29 -07004
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -07005"""
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
55from waflib import Task, TaskGen, Logs, Utils
56from waflib.Tools import c_preproc, cxx
57
58
59PCH_COMPILER_OPTIONS = {
60 'clang++': [['-include'], '.pch', ['-x', 'c++-header']],
61 'g++': [['-include'], '.gch', ['-x', 'c++-header']],
62}
63
Alexander Afanasyev1160baa2014-04-10 18:50:29 -070064
65def options(opt):
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070066 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 Afanasyev1160baa2014-04-10 18:50:29 -070067
68def configure(conf):
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070069 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 Afanasyev1160baa2014-04-10 18:50:29 -070075
76
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070077@TaskGen.feature('pch')
78@TaskGen.before('process_source')
79def apply_pch(self):
80 if not self.env.WITH_PCH:
81 return
82
83 if getattr(self.bld, 'pch_tasks', None) is None:
84 self.bld.pch_tasks = {}
85
86 if getattr(self, 'headers', None) is None:
87 return
88
89 self.headers = self.to_nodes(self.headers)
90
91 if getattr(self, 'name', None):
92 try:
93 task = self.bld.pch_tasks[self.name]
94 self.bld.fatal("Duplicated 'pch' task with name %r" % self.name)
95 except KeyError:
96 pass
97
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)
101
102 # target should be an absolute path of `out`, but without precompiled header extension
103 task.target = out.abspath()[:-len(out.suffix())]
104
105 self.pch_task = task
106 if getattr(self, 'name', None):
107 self.bld.pch_tasks[self.name] = task
108
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700109@TaskGen.feature('cxx')
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700110@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])
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700130
131class gchx(Task.Task):
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700132 run_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${CPPFLAGS} ${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()}'
133 scan = c_preproc.scan
134 color = 'BLUE'
135 ext_out=['.h']
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700136
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700137 def runnable_status(self):
138 ret = Task.Task.runnable_status(self)
139 if ret == Task.SKIP_ME and self.env.CXX_NAME == 'clang':
140 t = os.stat(self.outputs[0].abspath()).st_mtime
141 for n in self.inputs:
142 if os.stat(n.abspath()).st_mtime > t:
143 return Task.RUN_ME
144 return ret