waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

genpybind.py (7000B)


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