jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
6.8KB

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