From cc20b414b395387b280ae1997718362c1962f152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johann=20Kl=C3=A4hn?= Date: Thu, 19 Oct 2017 16:07:59 +0200 Subject: [PATCH 1/3] Add genpybind feature --- waflib/extras/genpybind.py | 183 +++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 waflib/extras/genpybind.py diff --git a/waflib/extras/genpybind.py b/waflib/extras/genpybind.py new file mode 100644 index 00000000..7cebe514 --- /dev/null +++ b/waflib/extras/genpybind.py @@ -0,0 +1,183 @@ +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") + + +@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() + + 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 From 0c16ab4f6544e63457e8d243fb3cfb65734514da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20St=C3=B6ckel?= Date: Mon, 29 Jan 2018 11:39:15 +0100 Subject: [PATCH 2/3] Set resource-dir explicitly for genpybind-parse --- waflib/extras/genpybind.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/waflib/extras/genpybind.py b/waflib/extras/genpybind.py index 7cebe514..ac206ee8 100644 --- a/waflib/extras/genpybind.py +++ b/waflib/extras/genpybind.py @@ -29,6 +29,16 @@ def configure(cfg): 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") @@ -78,7 +88,8 @@ class genpybind(Task.Task): # pylint: disable=invalid-name if not self.inputs: return - args = self.find_genpybind() + self._arguments() + args = self.find_genpybind() + self._arguments( + resource_dir=self.env.GENPYBIND_RESOURCE_DIR) output = self.run_genpybind(args) From 1fd2e4174cc3d4a3f099136f59447c4966913494 Mon Sep 17 00:00:00 2001 From: Yannik Stradmann Date: Tue, 2 Jul 2019 11:50:25 +0200 Subject: [PATCH 3/3] Add genpybind example --- playground/genpybind/example.cpp | 9 +++++++ playground/genpybind/example.h | 20 +++++++++++++++ playground/genpybind/example_test.py | 9 +++++++ playground/genpybind/wscript | 37 ++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 playground/genpybind/example.cpp create mode 100644 playground/genpybind/example.h create mode 100644 playground/genpybind/example_test.py create mode 100644 playground/genpybind/wscript diff --git a/playground/genpybind/example.cpp b/playground/genpybind/example.cpp new file mode 100644 index 00000000..a237e56f --- /dev/null +++ b/playground/genpybind/example.cpp @@ -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; } diff --git a/playground/genpybind/example.h b/playground/genpybind/example.h new file mode 100644 index 00000000..95636377 --- /dev/null +++ b/playground/genpybind/example.h @@ -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; +}; diff --git a/playground/genpybind/example_test.py b/playground/genpybind/example_test.py new file mode 100644 index 00000000..13843909 --- /dev/null +++ b/playground/genpybind/example_test.py @@ -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 diff --git a/playground/genpybind/wscript b/playground/genpybind/wscript new file mode 100644 index 00000000..1732ec83 --- /dev/null +++ b/playground/genpybind/wscript @@ -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'])