blob: 57582ad2834ef7878c26aac025f0ab9113fd4ffa [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()):
Alexander Afanasyevfc294ae2015-05-11 12:18:55 -070070 if Utils.unversioned_sys_platform() == "darwin" and conf.env['CXX_NAME'] == 'clang':
71 version = tuple(int(i) for i in conf.env['CC_VERSION'])
72 if version < (6, 1, 0):
73 # Issue #2804
74 return
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070075 conf.env.WITH_PCH = True
76 flags = PCH_COMPILER_OPTIONS[conf.env['COMPILER_CXX']]
77 conf.env.CXXPCH_F = flags[0]
78 conf.env.CXXPCH_EXT = flags[1]
79 conf.env.CXXPCH_FLAGS = flags[2]
Alexander Afanasyev1160baa2014-04-10 18:50:29 -070080
81
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070082@TaskGen.feature('pch')
83@TaskGen.before('process_source')
84def apply_pch(self):
85 if not self.env.WITH_PCH:
86 return
87
88 if getattr(self.bld, 'pch_tasks', None) is None:
89 self.bld.pch_tasks = {}
90
Davide Pesaventob1ab0ca2014-06-27 16:35:23 +020091 if getattr(self, 'headers', None) is None:
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -070092 return
93
94 self.headers = self.to_nodes(self.headers)
95
96 if getattr(self, 'name', None):
97 try:
Alexander Afanasyev5519cc72015-03-01 14:25:03 -080098 task = self.bld.pch_tasks["%s.%s" % (self.name, self.idx)]
99 self.bld.fatal("Duplicated 'pch' task with name %r" % "%s.%s" % (self.name, self.idx))
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700100 except KeyError:
101 pass
102
103 out = '%s.%d%s' % (self.target, self.idx, self.env['CXXPCH_EXT'])
104 out = self.path.find_or_declare(out)
105 task = self.create_task('gchx', self.headers, out)
106
107 # target should be an absolute path of `out`, but without precompiled header extension
108 task.target = out.abspath()[:-len(out.suffix())]
109
Davide Pesaventob1ab0ca2014-06-27 16:35:23 +0200110 self.pch_task = task
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700111 if getattr(self, 'name', None):
Alexander Afanasyev5519cc72015-03-01 14:25:03 -0800112 self.bld.pch_tasks["%s.%s" % (self.name, self.idx)] = task
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700113
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700114@TaskGen.feature('cxx')
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700115@TaskGen.after_method('process_source', 'propagate_uselib_vars')
116def add_pch(self):
117 if not (self.env['WITH_PCH'] and getattr(self, 'use', None) and getattr(self, 'compiled_tasks', None) and getattr(self.bld, 'pch_tasks', None)):
118 return
119
120 pch = None
121 # find pch task, if any
122
123 if getattr(self, 'pch_task', None):
124 pch = self.pch_task
125 else:
126 for use in Utils.to_list(self.use):
127 try:
128 pch = self.bld.pch_tasks[use]
129 except KeyError:
130 pass
131
132 if pch:
133 for x in self.compiled_tasks:
134 x.env.append_value('CXXFLAGS', self.env['CXXPCH_F'] + [pch.target])
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700135
136class gchx(Task.Task):
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700137 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()}'
138 scan = c_preproc.scan
139 color = 'BLUE'
140 ext_out=['.h']
Alexander Afanasyev1160baa2014-04-10 18:50:29 -0700141
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700142 def runnable_status(self):
Alexander Afanasyev24b75c82014-05-31 15:59:31 +0300143 try:
144 node_deps = self.generator.bld.node_deps[self.uid()]
145 except KeyError:
146 node_deps = []
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700147 ret = Task.Task.runnable_status(self)
148 if ret == Task.SKIP_ME and self.env.CXX_NAME == 'clang':
149 t = os.stat(self.outputs[0].abspath()).st_mtime
Alexander Afanasyev24b75c82014-05-31 15:59:31 +0300150 for n in self.inputs + node_deps:
Alexander Afanasyev8b1674a2014-05-15 00:58:43 -0700151 if os.stat(n.abspath()).st_mtime > t:
152 return Task.RUN_ME
153 return ret