clang_configuration.bzl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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 _detect_system_clang(repository_ctx):
  16. """Detects whether the system-provided clang can be used.
  17. Returns a tuple of (is_clang, environment).
  18. """
  19. # If the user provides an explicit `CC` environment variable, use that as
  20. # the compiler. This should point at the `clang` executable to use.
  21. cc = repository_ctx.os.environ.get("CC")
  22. if cc:
  23. version_output = _run(repository_ctx, [cc, "--version"]).stdout
  24. if not "clang" in version_output:
  25. fail("The `CC` environment variable is not a Clang compiler.")
  26. return repository_ctx.path(cc)
  27. # Try looking on the path. We check for the specific versioned name and then the normal name.
  28. system_clang = repository_ctx.which("clang-13")
  29. if not system_clang:
  30. system_clang = repository_ctx.which("clang")
  31. if not system_clang:
  32. fail("Unable to find a `clang` executable on the system path.")
  33. return system_clang
  34. def _compute_clang_resource_dir(repository_ctx, clang):
  35. """Runs the `clang` binary to get its resource dir."""
  36. output = _run(
  37. repository_ctx,
  38. [clang, "-no-canonical-prefixes", "--print-resource-dir"],
  39. ).stdout
  40. # The only line printed is this path.
  41. return output.splitlines()[0]
  42. def _compute_mac_os_sysroot(repository_ctx):
  43. """Runs `xcrun` to extract the correct sysroot."""
  44. xcrun = repository_ctx.which("xcrun")
  45. if not xcrun:
  46. fail("`xcrun` not found: is Xcode installed?")
  47. output = _run(repository_ctx, [xcrun, "--show-sdk-path"]).stdout
  48. return output.splitlines()[0]
  49. def _compute_clang_cpp_include_search_paths(repository_ctx, clang, sysroot):
  50. """Runs the `clang` binary and extracts the include search paths.
  51. Returns the resulting paths as a list of strings.
  52. """
  53. # The only way to get this out of Clang currently is to parse the verbose
  54. # output of the compiler when it is compiling C++ code.
  55. cmd = [
  56. clang,
  57. # Avoid canonicalizing away symlinks.
  58. "-no-canonical-prefixes",
  59. # Extract verbose output.
  60. "-v",
  61. # Just parse the input, don't generate outputs.
  62. "-fsyntax-only",
  63. # Force the language to be C++.
  64. "-x",
  65. "c++",
  66. # Read in an empty input file.
  67. "/dev/null",
  68. # Always use libc++.
  69. "-stdlib=libc++",
  70. ]
  71. # We need to use a sysroot to correctly represent a run on macOS.
  72. if repository_ctx.os.name.lower().startswith("mac os"):
  73. if not sysroot:
  74. fail("Must provide a sysroot on macOS!")
  75. cmd.append("--sysroot=" + sysroot)
  76. # Note that verbose output is on stderr, not stdout!
  77. output = _run(repository_ctx, cmd).stderr.splitlines()
  78. # Return the list of directories printed for system headers. These are the
  79. # only ones that Bazel needs us to manually provide. We find these by
  80. # searching for a begin and end marker. We also have to strip off a leading
  81. # space from each path.
  82. include_begin = output.index("#include <...> search starts here:") + 1
  83. include_end = output.index("End of search list.", include_begin)
  84. return [
  85. repository_ctx.path(s.lstrip(" "))
  86. for s in output[include_begin:include_end]
  87. ]
  88. def _configure_clang_toolchain_impl(repository_ctx):
  89. # First just symlink in the untemplated parts of the toolchain repo.
  90. repository_ctx.symlink(repository_ctx.attr._clang_toolchain_build, "BUILD")
  91. repository_ctx.symlink(
  92. repository_ctx.attr._clang_cc_toolchain_config,
  93. "cc_toolchain_config.bzl",
  94. )
  95. # Find a Clang C++ compiler, and where it lives. We need to walk symlinks
  96. # here as the other LLVM tools may not be symlinked into the PATH even if
  97. # `clang` is. We also insist on finding the basename of `clang++` as that is
  98. # important for C vs. C++ compiles.
  99. clang = _detect_system_clang(repository_ctx)
  100. clang = clang.realpath.dirname.get_child("clang++")
  101. # Compute the various directories used by Clang.
  102. resource_dir = _compute_clang_resource_dir(repository_ctx, clang)
  103. sysroot_dir = None
  104. if repository_ctx.os.name.lower().startswith("mac os"):
  105. sysroot_dir = _compute_mac_os_sysroot(repository_ctx)
  106. include_dirs = _compute_clang_cpp_include_search_paths(
  107. repository_ctx,
  108. clang,
  109. sysroot_dir,
  110. )
  111. repository_ctx.template(
  112. "clang_detected_variables.bzl",
  113. repository_ctx.attr._clang_detected_variables_template,
  114. substitutions = {
  115. "{LLVM_BINDIR}": str(clang.dirname),
  116. "{CLANG_RESOURCE_DIR}": resource_dir,
  117. "{CLANG_INCLUDE_DIRS_LIST}": str(
  118. [str(path) for path in include_dirs],
  119. ),
  120. "{SYSROOT}": str(sysroot_dir),
  121. },
  122. executable = False,
  123. )
  124. configure_clang_toolchain = repository_rule(
  125. implementation = _configure_clang_toolchain_impl,
  126. configure = True,
  127. local = True,
  128. attrs = {
  129. "_clang_toolchain_build": attr.label(
  130. default = Label("//bazel/cc_toolchains:clang_toolchain.BUILD"),
  131. allow_single_file = True,
  132. ),
  133. "_clang_cc_toolchain_config": attr.label(
  134. default = Label(
  135. "//bazel/cc_toolchains:clang_cc_toolchain_config.bzl",
  136. ),
  137. allow_single_file = True,
  138. ),
  139. "_clang_detected_variables_template": attr.label(
  140. default = Label(
  141. "//bazel/cc_toolchains:clang_detected_variables.tpl.bzl",
  142. ),
  143. allow_single_file = True,
  144. ),
  145. },
  146. environ = ["CC"],
  147. )