|
- 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
|