1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
import os
import pipes
import subprocess
import sys
from waflib import Logs, Task, Context
from waflib.Tools.c_preproc import scan as scan_impl
# ^-- Note: waflib.extras.gccdeps.scan does not work for us,
# due to its current implementation:
# The -MD flag is injected into the {C,CXX}FLAGS environment variable and
# dependencies are read out in a separate step after compiling by reading
# the .d file saved alongside the object file.
# As the genpybind task refers to a header file that is never compiled itself,
# gccdeps will not be able to extract the list of dependencies.
from waflib.TaskGen import feature, before_method
def join_args(args):
return " ".join(pipes.quote(arg) for arg in args)
def configure(cfg):
cfg.load("compiler_cxx")
cfg.load("python")
cfg.check_python_version(minver=(2, 7))
if not cfg.env.LLVM_CONFIG:
cfg.find_program("llvm-config", var="LLVM_CONFIG")
if not cfg.env.GENPYBIND:
cfg.find_program("genpybind", var="GENPYBIND")
# find clang reasource dir for builtin headers
cfg.env.GENPYBIND_RESOURCE_DIR = os.path.join(
cfg.cmd_and_log(cfg.env.LLVM_CONFIG + ["--libdir"]).strip(),
"clang",
cfg.cmd_and_log(cfg.env.LLVM_CONFIG + ["--version"]).strip())
if os.path.exists(cfg.env.GENPYBIND_RESOURCE_DIR):
cfg.msg("Checking clang resource dir", cfg.env.GENPYBIND_RESOURCE_DIR)
else:
cfg.fatal("Clang resource dir not found")
@feature("genpybind")
@before_method("process_source")
def generate_genpybind_source(self):
"""
Run genpybind on the headers provided in `source` and compile/link the
generated code instead. This works by generating the code on the fly and
swapping the source node before `process_source` is run.
"""
# name of module defaults to name of target
module = getattr(self, "module", self.target)
# create temporary source file in build directory to hold generated code
out = "genpybind-%s.%d.cpp" % (module, self.idx)
out = self.path.get_bld().find_or_declare(out)
task = self.create_task("genpybind", self.to_nodes(self.source), out)
# used to detect whether CFLAGS or CXXFLAGS should be passed to genpybind
task.features = self.features
task.module = module
# can be used to select definitions to include in the current module
# (when header files are shared by more than one module)
task.genpybind_tags = self.to_list(getattr(self, "genpybind_tags", []))
# additional include directories
task.includes = self.to_list(getattr(self, "includes", []))
task.genpybind = self.env.GENPYBIND
# Tell waf to compile/link the generated code instead of the headers
# originally passed-in via the `source` parameter. (see `process_source`)
self.source = [out]
class genpybind(Task.Task): # pylint: disable=invalid-name
"""
Runs genpybind on headers provided as input to this task.
Generated code will be written to the first (and only) output node.
"""
quiet = True
color = "PINK"
scan = scan_impl
@staticmethod
def keyword():
return "Analyzing"
def run(self):
if not self.inputs:
return
args = self.find_genpybind() + self._arguments(
resource_dir=self.env.GENPYBIND_RESOURCE_DIR)
output = self.run_genpybind(args)
# For debugging / log output
pasteable_command = join_args(args)
# write generated code to file in build directory
# (will be compiled during process_source stage)
(output_node,) = self.outputs
output_node.write("// {}\n{}\n".format(
pasteable_command.replace("\n", "\n// "), output))
def find_genpybind(self):
return self.genpybind
def run_genpybind(self, args):
bld = self.generator.bld
kwargs = dict(cwd=bld.variant_dir)
if hasattr(bld, "log_command"):
bld.log_command(args, kwargs)
else:
Logs.debug("runner: {!r}".format(args))
proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
stdout, stderr = proc.communicate()
if not isinstance(stdout, str):
stdout = stdout.decode(sys.stdout.encoding, errors="replace")
if not isinstance(stderr, str):
stderr = stderr.decode(sys.stderr.encoding, errors="replace")
if proc.returncode != 0:
bld.fatal(
"genpybind returned {code} during the following call:"
"\n{command}\n\n{stdout}\n\n{stderr}".format(
code=proc.returncode,
command=join_args(args),
stdout=stdout,
stderr=stderr,
))
if stderr.strip():
Logs.debug("non-fatal warnings during genpybind run:\n{}".format(stderr))
return stdout
def _include_paths(self):
return self.generator.to_incnodes(self.includes + self.env.INCLUDES)
def _inputs_as_relative_includes(self):
include_paths = self._include_paths()
relative_includes = []
for node in self.inputs:
for inc in include_paths:
if node.is_child_of(inc):
relative_includes.append(node.path_from(inc))
break
else:
self.generator.bld.fatal("could not resolve {}".format(node))
return relative_includes
def _arguments(self, genpybind_parse=None, resource_dir=None):
args = []
relative_includes = self._inputs_as_relative_includes()
is_cxx = "cxx" in self.features
# options for genpybind
args.extend(["--genpybind-module", self.module])
if self.genpybind_tags:
args.extend(["--genpybind-tag"] + self.genpybind_tags)
if relative_includes:
args.extend(["--genpybind-include"] + relative_includes)
if genpybind_parse:
args.extend(["--genpybind-parse", genpybind_parse])
args.append("--")
# headers to be processed by genpybind
args.extend(node.abspath() for node in self.inputs)
args.append("--")
# options for clang/genpybind-parse
args.append("-D__GENPYBIND__")
args.append("-xc++" if is_cxx else "-xc")
has_std_argument = False
for flag in self.env["CXXFLAGS" if is_cxx else "CFLAGS"]:
flag = flag.replace("-std=gnu", "-std=c")
if flag.startswith("-std=c"):
has_std_argument = True
args.append(flag)
if not has_std_argument:
args.append("-std=c++14")
args.extend("-I{}".format(n.abspath()) for n in self._include_paths())
args.extend("-D{}".format(p) for p in self.env.DEFINES)
# point to clang resource dir, if specified
if resource_dir:
args.append("-resource-dir={}".format(resource_dir))
return args
|