clang_configuration.bzl 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. # Exceptions. See /LICENSE for license information.
  3. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. """Starlark repository rules to configure Clang (and LLVM) toolchain for Bazel.
  5. These rules should be run from the `WORKSPACE` file to substitute appropriate
  6. configured values into a `clang_detected_variables.bzl` file that can be used
  7. by the actual toolchain configuration.
  8. """
  9. def _run(repository_ctx, cmd):
  10. """Runs the provided `cmd`, checks for failure, and returns the result."""
  11. exec_result = repository_ctx.execute(cmd)
  12. if exec_result.return_code != 0:
  13. fail("Unable to run command successfully: %s" % str(cmd))
  14. return exec_result
  15. def _compute_clang_resource_dir(repository_ctx, clang):
  16. """Runs the `clang` binary to get its resource dir."""
  17. output = _run(
  18. repository_ctx,
  19. [clang, "-no-canonical-prefixes", "--print-resource-dir"],
  20. ).stdout
  21. # The only line printed is this path.
  22. return output.splitlines()[0]
  23. def _compute_mac_os_sysroot(repository_ctx):
  24. """Runs `xcrun` to extract the correct sysroot."""
  25. xcrun = repository_ctx.which("xcrun")
  26. if not xcrun:
  27. fail("`xcrun` not found: is Xcode installed?")
  28. output = _run(repository_ctx, [xcrun, "--show-sdk-path"]).stdout
  29. return output.splitlines()[0]
  30. def _compute_clang_cpp_include_search_paths(repository_ctx, clang, sysroot):
  31. """Runs the `clang` binary and extracts the include search paths.
  32. Returns the resulting paths as a list of strings.
  33. """
  34. # The only way to get this out of Clang currently is to parse the verbose
  35. # output of the compiler when it is compiling C++ code.
  36. cmd = [
  37. clang,
  38. # Avoid canonicalizing away symlinks.
  39. "-no-canonical-prefixes",
  40. # Extract verbose output.
  41. "-v",
  42. # Just parse the input, don't generate outputs.
  43. "-fsyntax-only",
  44. # Force the language to be C++.
  45. "-x",
  46. "c++",
  47. # Read in an empty input file.
  48. "/dev/null",
  49. # Always use libc++.
  50. "-stdlib=libc++",
  51. ]
  52. # We need to use a sysroot to correctly represent a run on macOS.
  53. if repository_ctx.os.name.lower().startswith("mac os"):
  54. if not sysroot:
  55. fail("Must provide a sysroot on macOS!")
  56. cmd.append("--sysroot=" + sysroot)
  57. # Note that verbose output is on stderr, not stdout!
  58. output = _run(repository_ctx, cmd).stderr.splitlines()
  59. # Return the list of directories printed for system headers. These are the
  60. # only ones that Bazel needs us to manually provide. We find these by
  61. # searching for a begin and end marker. We also have to strip off a leading
  62. # space from each path.
  63. include_begin = output.index("#include <...> search starts here:") + 1
  64. include_end = output.index("End of search list.", include_begin)
  65. return [
  66. repository_ctx.path(s.lstrip(" "))
  67. for s in output[include_begin:include_end]
  68. ]
  69. def _configure_clang_toolchain_impl(repository_ctx):
  70. # First just symlink in the untemplated parts of the toolchain repo.
  71. repository_ctx.symlink(repository_ctx.attr._clang_toolchain_build, "BUILD")
  72. repository_ctx.symlink(
  73. repository_ctx.attr._clang_cc_toolchain_config,
  74. "cc_toolchain_config.bzl",
  75. )
  76. # Run the bootstrapped clang to detect relevant features for the toolchain.
  77. clang = repository_ctx.path(repository_ctx.attr.clang)
  78. if clang.basename != "clang":
  79. fail("The provided Clang binary must be `clang`, but is `%s` (%s)" %
  80. (clang.basename, str(clang)))
  81. # Adjust this to the "clang++" binary to ensure we get the correct behavior
  82. # when configuring it.
  83. clang = repository_ctx.path(str(clang) + "++")
  84. resource_dir = _compute_clang_resource_dir(repository_ctx, clang)
  85. sysroot_dir = None
  86. if repository_ctx.os.name.lower().startswith("mac os"):
  87. sysroot_dir = _compute_mac_os_sysroot(repository_ctx)
  88. include_dirs = _compute_clang_cpp_include_search_paths(
  89. repository_ctx,
  90. clang,
  91. sysroot_dir,
  92. )
  93. repository_ctx.template(
  94. "clang_detected_variables.bzl",
  95. repository_ctx.attr._clang_detected_variables_template,
  96. substitutions = {
  97. "{LLVM_BINDIR}": str(clang.dirname),
  98. "{CLANG_RESOURCE_DIR}": resource_dir,
  99. "{CLANG_INCLUDE_DIRS_LIST}": str(
  100. [str(path) for path in include_dirs],
  101. ),
  102. "{SYSROOT}": str(sysroot_dir),
  103. },
  104. executable = False,
  105. )
  106. configure_clang_toolchain = repository_rule(
  107. implementation = _configure_clang_toolchain_impl,
  108. configure = True,
  109. attrs = {
  110. "_clang_toolchain_build": attr.label(
  111. default = Label("//bazel/cc_toolchains:clang_toolchain.BUILD"),
  112. allow_single_file = True,
  113. ),
  114. "_clang_cc_toolchain_config": attr.label(
  115. default = Label(
  116. "//bazel/cc_toolchains:clang_cc_toolchain_config.bzl",
  117. ),
  118. allow_single_file = True,
  119. ),
  120. "_clang_detected_variables_template": attr.label(
  121. default = Label(
  122. "//bazel/cc_toolchains:clang_detected_variables.tpl.bzl",
  123. ),
  124. allow_single_file = True,
  125. ),
  126. # This must point at the `clang` binary inside a full LLVM toolchain
  127. # installation.
  128. "clang": attr.label(
  129. allow_single_file = True,
  130. mandatory = True,
  131. ),
  132. },
  133. )