Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 1 | #! /usr/bin/env python |
| 2 | # encoding: utf-8 |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 3 | # Alexander Afanasyev (UCLA), 2014 |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 4 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 5 | """ |
| 6 | Enable precompiled C++ header support (currently only clang++ and g++ are supported) |
| 7 | |
| 8 | To 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 | |
| 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. |
| 52 | """ |
| 53 | |
| 54 | import os |
| 55 | from waflib import Task, TaskGen, Logs, Utils |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 56 | from waflib.Tools import c_preproc, cxx |
| 57 | |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 58 | |
| 59 | PCH_COMPILER_OPTIONS = { |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 60 | 'clang++': [['-include'], '.pch', ['-x', 'c++-header']], |
| 61 | 'g++': [['-include'], '.gch', ['-x', 'c++-header']], |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 62 | } |
| 63 | |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 64 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 65 | def 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 Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 67 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 68 | def 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 Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 75 | |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 76 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 77 | @TaskGen.feature('pch') |
| 78 | @TaskGen.before('process_source') |
| 79 | def apply_pch(self): |
| 80 | if not self.env.WITH_PCH: |
| 81 | return |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 82 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 83 | if getattr(self.bld, 'pch_tasks', None) is None: |
| 84 | self.bld.pch_tasks = {} |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 85 | |
Davide Pesavento | dc26b27 | 2014-06-27 16:41:51 +0200 | [diff] [blame] | 86 | if getattr(self, 'headers', None) is None: |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 87 | return |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 88 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 89 | self.headers = self.to_nodes(self.headers) |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 90 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 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 |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 97 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 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) |
Alexander Afanasyev | 8552a38 | 2014-05-15 20:13:42 -0700 | [diff] [blame] | 101 | |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 102 | # target should be an absolute path of `out`, but without precompiled header extension |
| 103 | task.target = out.abspath()[:-len(out.suffix())] |
| 104 | |
Davide Pesavento | dc26b27 | 2014-06-27 16:41:51 +0200 | [diff] [blame] | 105 | self.pch_task = task |
Alexander Afanasyev | 58ea458 | 2014-05-30 08:38:56 +0300 | [diff] [blame] | 106 | if getattr(self, 'name', None): |
| 107 | self.bld.pch_tasks[self.name] = task |
| 108 | |
| 109 | @TaskGen.feature('cxx') |
| 110 | @TaskGen.after_method('process_source', 'propagate_uselib_vars') |
| 111 | def 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 | |
| 131 | class gchx(Task.Task): |
| 132 | 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'] |
| 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 |