1#! /usr/bin/env python 2# encoding: utf-8 3# Alexander Afanasyev (UCLA), 2014 4 5""" 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, 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 64 65def 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++)''') 67 68def 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] 75 76 77@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" % "%s.%s" % (self.name, self.idx)) 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 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): 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 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 149