Ashlesh Gawande | 0b2897e | 2018-06-20 14:40:47 -0500 | [diff] [blame^] | 1 | #! /usr/bin/env python |
| 2 | # encoding: utf-8 |
| 3 | # Alexander Afanasyev (UCLA), 2014 |
| 4 | |
| 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 |
| 56 | from waflib.Tools import c_preproc, cxx |
| 57 | |
| 58 | |
| 59 | PCH_COMPILER_OPTIONS = { |
| 60 | 'clang++': [['-include'], '.pch', ['-x', 'c++-header']], |
| 61 | 'g++': [['-include'], '.gch', ['-x', 'c++-header']], |
| 62 | } |
| 63 | |
| 64 | |
| 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++)''') |
| 67 | |
| 68 | def configure(conf): |
| 69 | if (conf.options.with_pch and conf.env['COMPILER_CXX'] in PCH_COMPILER_OPTIONS.keys()): |
| 70 | 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 |
| 75 | 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] |
| 80 | |
| 81 | |
| 82 | @TaskGen.feature('pch') |
| 83 | @TaskGen.before('process_source') |
| 84 | def 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 | |
| 91 | if getattr(self, 'headers', None) is None: |
| 92 | return |
| 93 | |
| 94 | self.headers = self.to_nodes(self.headers) |
| 95 | |
| 96 | if getattr(self, 'name', None): |
| 97 | try: |
| 98 | task = self.bld.pch_tasks[self.name] |
| 99 | self.bld.fatal("Duplicated 'pch' task with name %r" % self.name) |
| 100 | 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 | |
| 110 | self.pch_task = task |
| 111 | if getattr(self, 'name', None): |
| 112 | self.bld.pch_tasks[self.name] = task |
| 113 | |
| 114 | @TaskGen.feature('cxx') |
| 115 | @TaskGen.after_method('process_source', 'propagate_uselib_vars') |
| 116 | def 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]) |
| 135 | |
| 136 | class gchx(Task.Task): |
| 137 | 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'] |
| 141 | |
| 142 | def runnable_status(self): |
| 143 | try: |
| 144 | node_deps = self.generator.bld.node_deps[self.uid()] |
| 145 | except KeyError: |
| 146 | node_deps = [] |
| 147 | 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 |
| 150 | for n in self.inputs + node_deps: |
| 151 | if os.stat(n.abspath()).st_mtime > t: |
| 152 | return Task.RUN_ME |
| 153 | return ret |