mirror of https://gitlab.com/ita1024/waf.git
Merge branch 'genpybind' into 'master'
Add 'genpybind' feature See merge request ita1024/waf!2244
This commit is contained in:
commit
fbe75085c6
|
@ -0,0 +1,9 @@
|
|||
#include "example.h"
|
||||
|
||||
constexpr int Example::not_exposed;
|
||||
|
||||
int Example::calculate(int some_argument) const { return _value + some_argument; }
|
||||
|
||||
int Example::getSomething() const { return _value; }
|
||||
|
||||
void Example::setSomething(int value) { _value = value; }
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "genpybind.h"
|
||||
|
||||
class GENPYBIND(visible) Example {
|
||||
public:
|
||||
static constexpr int GENPYBIND(hidden) not_exposed = 10;
|
||||
|
||||
/// \brief Do a complicated calculation.
|
||||
int calculate(int some_argument = 5) const;
|
||||
|
||||
GENPYBIND(getter_for(something))
|
||||
int getSomething() const;
|
||||
|
||||
GENPYBIND(setter_for(something))
|
||||
void setSomething(int value);
|
||||
|
||||
private:
|
||||
int _value = 0;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import pyexample as m
|
||||
|
||||
|
||||
def test_example():
|
||||
obj = m.Example()
|
||||
obj.something = 42
|
||||
assert obj.something == 42
|
||||
assert obj.calculate() == 47 # with default argument
|
||||
assert obj.calculate(2) == 44
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
def options(opt):
|
||||
opt.load('python')
|
||||
opt.load('compiler_cxx')
|
||||
opt.load('genpybind')
|
||||
|
||||
|
||||
def configure(cfg):
|
||||
cfg.load('python')
|
||||
cfg.load('compiler_cxx')
|
||||
cfg.check_python_version((2, 7))
|
||||
cfg.check_python_headers()
|
||||
cfg.load('genpybind')
|
||||
|
||||
cfg.check(compiler='cxx',
|
||||
features='cxx pyext',
|
||||
uselib_store='PYBIND11GENPYBIND_EXAMPLE',
|
||||
mandatory=True,
|
||||
header_name='pybind11/pybind11.h')
|
||||
|
||||
|
||||
def build(bld):
|
||||
bld(target='example_inc',
|
||||
export_includes='.')
|
||||
|
||||
bld.shlib(target='example',
|
||||
source='example.cpp',
|
||||
features='use',
|
||||
use='example_inc')
|
||||
|
||||
bld(target='pyexample',
|
||||
source='example.h',
|
||||
genpybind_tags='genpybind_example',
|
||||
features='use genpybind cxx cxxshlib pyext',
|
||||
use=['PYBIND11GENPYBIND_EXAMPLE', 'example'])
|
|
@ -0,0 +1,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
|
Loading…
Reference in New Issue