1# Copyright 2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Repository rule that tries to find system provided LLVM packages.""" 16 17load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 18 19SYSTEM_LLVM_BAZEL_TEMPLATE = """package(default_visibility = ["//visibility:public"]) 20# Create one hidden library with all LLVM headers that depends on all its 21# static library archives. This will be used to provide individual library 22# targets named the same as the upstream Bazel files. 23cc_library( 24 name = "llvm", 25 hdrs = glob([ 26 "llvm-project-include/clang-c/**/*.h", 27 "llvm-project-include/clang/**/*.def", 28 "llvm-project-include/clang/**/*.h", 29 "llvm-project-include/clang/**/*.inc", 30 "llvm-project-include/llvm-c/**/*.h", 31 "llvm-project-include/llvm/**/*.def", 32 "llvm-project-include/llvm/**/*.h", 33 "llvm-project-include/llvm/**/*.inc", 34 ]), 35 includes = ["llvm-project-include"], 36 linkopts = [ 37 "-lncurses", 38 %{llvm_system_libs} 39 %{llvm_lib_dir} 40 "-Wl,--start-group", 41 %{llvm_libs} 42 "-Wl,--end-group", 43 ], 44 visibility = ["@llvm-project//clang:__pkg__"], 45) 46# Fake llvm libraries 47cc_library(name = "Support", deps = ["@llvm-project//llvm:llvm"]) 48cc_library(name = "config", deps = ["@llvm-project//llvm:llvm"]) 49""" 50 51SYSTEM_CLANG_BAZEL = """package(default_visibility = ["//visibility:public"]) 52# Fake libraries that just depend on a big library with all files. 53cc_library(name = "ast", deps = ["@llvm-project//llvm:llvm"]) 54cc_library(name = "basic", deps = ["@llvm-project//llvm:llvm"]) 55cc_library(name = "driver", deps = ["@llvm-project//llvm:llvm"]) 56cc_library(name = "format", deps = ["@llvm-project//llvm:llvm"]) 57cc_library(name = "frontend", deps = ["@llvm-project//llvm:llvm"]) 58cc_library(name = "lex", deps = ["@llvm-project//llvm:llvm"]) 59cc_library(name = "serialization", deps = ["@llvm-project//llvm:llvm"]) 60cc_library(name = "tooling", deps = ["@llvm-project//llvm:llvm"]) 61cc_library(name = "tooling_core", deps = ["@llvm-project//llvm:llvm"]) 62""" 63 64def _use_system_llvm(ctx): 65 # Look for LLVM in known places 66 llvm_config_tool = ctx.execute( 67 ["which"] + # Prints all arguments it finds in the system PATH 68 ["llvm-config-{}".format(ver) for ver in range(20, 10, -1)] + 69 ["llvm-config"], 70 ).stdout.splitlines() 71 if not llvm_config_tool: 72 return False 73 74 llvm_config = ctx.execute([ 75 llvm_config_tool[0], 76 "--link-static", 77 "--includedir", # Output line 0 78 "--libdir", # Output line 1 79 "--libs", # Output line 2 80 "--system-libs", # Output line 3 81 "engine", 82 "option", 83 ]).stdout.splitlines() 84 if not llvm_config: 85 return False 86 87 include_dir = llvm_config[0] 88 for suffix in ["llvm", "llvm-c", "clang", "clang-c"]: 89 ctx.symlink( 90 include_dir + "/" + suffix, 91 "llvm/llvm-project-include/" + suffix, 92 ) 93 94 system_libs = llvm_config[3].split(" ") 95 lib_dir = llvm_config[1].split(" ")[0] 96 97 # Sadly there's no easy way to get to the Clang library archives 98 archives = ctx.execute( 99 ["find", ".", "-maxdepth", "1"] + 100 ["(", "-name", "libLLVM*.a", "-o", "-name", "libclang*.a", ")"], 101 working_directory = lib_dir, 102 ).stdout.splitlines() 103 lib_strs = sorted(["\"-l{}\",".format(a[5:-2]) for a in archives]) 104 105 ctx.file( 106 "llvm/BUILD.bazel", 107 SYSTEM_LLVM_BAZEL_TEMPLATE.replace( 108 "%{llvm_system_libs}", 109 "\n".join(["\"{}\",".format(s) for s in system_libs]), 110 ).replace( 111 "%{llvm_lib_dir}", 112 "\"-L{}\",".format(lib_dir), 113 ).replace( 114 "%{llvm_libs}", 115 "\n".join(lib_strs), 116 ), 117 ) 118 ctx.file("clang/BUILD.bazel", SYSTEM_CLANG_BAZEL) 119 return True 120 121def _overlay_directories(ctx, src_path, target_path): 122 bazel_path = src_path.get_child("utils").get_child("bazel") 123 overlay_path = bazel_path.get_child("llvm-project-overlay") 124 script_path = bazel_path.get_child("overlay_directories.py") 125 126 python_bin = ctx.which("python3") 127 if not python_bin: 128 python_bin = ctx.which("python") 129 130 if not python_bin: 131 fail("Failed to find python3 binary") 132 133 cmd = [ 134 python_bin, 135 script_path, 136 "--src", 137 src_path, 138 "--overlay", 139 overlay_path, 140 "--target", 141 target_path, 142 ] 143 exec_result = ctx.execute(cmd, timeout = 20) 144 145 if exec_result.return_code != 0: 146 fail(("Failed to execute overlay script: '{cmd}'\n" + 147 "Exited with code {return_code}\n" + 148 "stdout:\n{stdout}\n" + 149 "stderr:\n{stderr}\n").format( 150 cmd = " ".join([str(arg) for arg in cmd]), 151 return_code = exec_result.return_code, 152 stdout = exec_result.stdout, 153 stderr = exec_result.stderr, 154 )) 155 156DEFAULT_LLVM_COMMIT = "2c494f094123562275ae688bd9e946ae2a0b4f8b" # 2022-03-31 157DEFAULT_LLVM_SHA256 = "59b9431ae22f0ea5f2ce880925c0242b32a9e4f1ae8147deb2bb0fc19b53fa0d" 158 159def _llvm_configure_impl(ctx): 160 commit = ctx.attr.commit 161 sha256 = ctx.attr.sha256 162 163 if ctx.attr.system_libraries: 164 if _use_system_llvm(ctx): 165 return 166 if not commit: 167 fail(( 168 "Failed to find LLVM and clang system libraries\n\n" + 169 "Note: You may have to install llvm-13-dev and libclang-13-dev\n" + 170 " packages (or later versions) first.\n" 171 )) 172 173 if not commit: 174 commit = DEFAULT_LLVM_COMMIT 175 sha256 = DEFAULT_LLVM_SHA256 176 177 ctx.download_and_extract( 178 ["https://github.com/llvm/llvm-project/archive/{commit}.tar.gz".format(commit = commit)], 179 "llvm-raw", 180 sha256, 181 "", 182 "llvm-project-" + commit, 183 ) 184 185 target_path = ctx.path("llvm-raw").dirname 186 src_path = target_path.get_child("llvm-raw") 187 _overlay_directories(ctx, src_path, target_path) 188 189 # Create a starlark file with the requested LLVM targets 190 ctx.file( 191 "llvm/targets.bzl", 192 "llvm_targets = " + str(ctx.attr.targets), 193 executable = False, 194 ) 195 196 # Set up C++ toolchain options. LLVM requires at least C++ 14. 197 ctx.file( 198 ".bazelrc", 199 "build --cxxopt=-std=c++17 --host_cxxopt=-std=c++17", 200 executable = False, 201 ) 202 203DEFAULT_TARGETS = ["AArch64", "ARM", "PowerPC", "X86"] 204 205llvm_configure = repository_rule( 206 implementation = _llvm_configure_impl, 207 local = True, 208 configure = True, 209 attrs = { 210 "system_libraries": attr.bool(default = True), 211 "commit": attr.string(), 212 "sha256": attr.string(), 213 "targets": attr.string_list(default = DEFAULT_TARGETS), 214 }, 215) 216 217def _llvm_zlib_disable_impl(ctx): 218 ctx.file( 219 "BUILD.bazel", 220 """cc_library(name = "zlib", visibility = ["//visibility:public"])""", 221 executable = False, 222 ) 223 224llvm_zlib_disable = repository_rule( 225 implementation = _llvm_zlib_disable_impl, 226) 227 228def _llvm_terminfo_disable(ctx): 229 ctx.file( 230 "BUILD.bazel", 231 """cc_library(name = "terminfo", visibility = ["//visibility:public"])""", 232 executable = False, 233 ) 234 235llvm_terminfo_disable = repository_rule( 236 implementation = _llvm_terminfo_disable, 237) 238 239def llvm_disable_optional_support_deps(): 240 maybe(llvm_zlib_disable, name = "llvm_zlib") 241 maybe(llvm_terminfo_disable, name = "llvm_terminfo") 242