1# This file is licensed under the Apache License v2.0 with LLVM Exceptions. 2# See https://llvm.org/LICENSE.txt for license information. 3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 4 5"""Helper macros to configure the LLVM overlay project.""" 6 7# Directory of overlay files relative to WORKSPACE 8DEFAULT_OVERLAY_PATH = "llvm-project-overlay" 9 10DEFAULT_TARGETS = [ 11 "AArch64", 12 "AMDGPU", 13 "ARM", 14 "AVR", 15 "BPF", 16 "Hexagon", 17 "Lanai", 18 "LoongArch", 19 "Mips", 20 "MSP430", 21 "NVPTX", 22 "PowerPC", 23 "RISCV", 24 "Sparc", 25 "SPIRV", 26 "SystemZ", 27 "VE", 28 "WebAssembly", 29 "X86", 30 "XCore", 31] 32 33def _overlay_directories(repository_ctx): 34 src_path = repository_ctx.path(Label("@llvm-raw//:WORKSPACE")).dirname 35 bazel_path = src_path.get_child("utils").get_child("bazel") 36 overlay_path = bazel_path.get_child("llvm-project-overlay") 37 script_path = bazel_path.get_child("overlay_directories.py") 38 39 python_bin = repository_ctx.which("python3") 40 if not python_bin: 41 # Windows typically just defines "python" as python3. The script itself 42 # contains a check to ensure python3. 43 python_bin = repository_ctx.which("python") 44 45 if not python_bin: 46 fail("Failed to find python3 binary") 47 48 cmd = [ 49 python_bin, 50 script_path, 51 "--src", 52 src_path, 53 "--overlay", 54 overlay_path, 55 "--target", 56 ".", 57 ] 58 exec_result = repository_ctx.execute(cmd, timeout = 20) 59 60 if exec_result.return_code != 0: 61 fail(("Failed to execute overlay script: '{cmd}'\n" + 62 "Exited with code {return_code}\n" + 63 "stdout:\n{stdout}\n" + 64 "stderr:\n{stderr}\n").format( 65 cmd = " ".join([str(arg) for arg in cmd]), 66 return_code = exec_result.return_code, 67 stdout = exec_result.stdout, 68 stderr = exec_result.stderr, 69 )) 70 71def _extract_cmake_settings(repository_ctx, llvm_cmake): 72 # The list to be written to vars.bzl 73 # `CMAKE_CXX_STANDARD` may be used from WORKSPACE for the toolchain. 74 c = { 75 "CMAKE_CXX_STANDARD": None, 76 "LLVM_VERSION_MAJOR": None, 77 "LLVM_VERSION_MINOR": None, 78 "LLVM_VERSION_PATCH": None, 79 "LLVM_VERSION_SUFFIX": None, 80 } 81 82 # It would be easier to use external commands like sed(1) and python. 83 # For portability, the parser should run on Starlark. 84 llvm_cmake_path = repository_ctx.path(Label("//:" + llvm_cmake)) 85 for line in repository_ctx.read(llvm_cmake_path).splitlines(): 86 # Extract "set ( FOO bar ... " 87 setfoo = line.partition("(") 88 if setfoo[1] != "(": 89 continue 90 if setfoo[0].strip().lower() != "set": 91 continue 92 93 # `kv` is assumed as \s*KEY\s+VAL\s*\).* 94 # Typical case is like 95 # LLVM_REQUIRED_CXX_STANDARD 17) 96 # Possible case -- It should be ignored. 97 # CMAKE_CXX_STANDARD ${...} CACHE STRING "...") 98 kv = setfoo[2].strip() 99 i = kv.find(" ") 100 if i < 0: 101 continue 102 k = kv[:i] 103 104 # Prefer LLVM_REQUIRED_CXX_STANDARD instead of CMAKE_CXX_STANDARD 105 if k == "LLVM_REQUIRED_CXX_STANDARD": 106 k = "CMAKE_CXX_STANDARD" 107 c[k] = None 108 if k not in c: 109 continue 110 111 # Skip if `CMAKE_CXX_STANDARD` is set with 112 # `LLVM_REQUIRED_CXX_STANDARD`. 113 # Then `v` will not be desired form, like "${...} CACHE" 114 if c[k] != None: 115 continue 116 117 # Pick up 1st word as the value. 118 # Note: It assumes unquoted word. 119 v = kv[i:].strip().partition(")")[0].partition(" ")[0] 120 c[k] = v 121 122 # Synthesize `LLVM_VERSION` for convenience. 123 c["LLVM_VERSION"] = "{}.{}.{}".format( 124 c["LLVM_VERSION_MAJOR"], 125 c["LLVM_VERSION_MINOR"], 126 c["LLVM_VERSION_PATCH"], 127 ) 128 129 c["PACKAGE_VERSION"] = "{}.{}.{}{}".format( 130 c["LLVM_VERSION_MAJOR"], 131 c["LLVM_VERSION_MINOR"], 132 c["LLVM_VERSION_PATCH"], 133 c["LLVM_VERSION_SUFFIX"], 134 ) 135 136 return c 137 138def _write_dict_to_file(repository_ctx, filepath, header, vars): 139 # (fci + individual vars) + (fcd + dict items) + (fct) 140 fci = header 141 fcd = "\nllvm_vars={\n" 142 fct = "}\n" 143 144 for k, v in vars.items(): 145 fci += '{} = "{}"\n'.format(k, v) 146 fcd += ' "{}": "{}",\n'.format(k, v) 147 148 repository_ctx.file(filepath, content = fci + fcd + fct) 149 150def _llvm_configure_impl(repository_ctx): 151 _overlay_directories(repository_ctx) 152 153 llvm_cmake = "llvm/CMakeLists.txt" 154 vars = _extract_cmake_settings( 155 repository_ctx, 156 llvm_cmake, 157 ) 158 159 # Grab version info and merge it with the other vars 160 version = _extract_cmake_settings( 161 repository_ctx, 162 "cmake/Modules/LLVMVersion.cmake", 163 ) 164 version = {k: v for k, v in version.items() if v != None} 165 vars.update(version) 166 167 _write_dict_to_file( 168 repository_ctx, 169 filepath = "vars.bzl", 170 header = "# Generated from {}\n\n".format(llvm_cmake), 171 vars = vars, 172 ) 173 174 # Create a starlark file with the requested LLVM targets. 175 targets = repository_ctx.attr.targets 176 repository_ctx.file( 177 "llvm/targets.bzl", 178 content = "llvm_targets = " + str(targets), 179 executable = False, 180 ) 181 182llvm_configure = repository_rule( 183 implementation = _llvm_configure_impl, 184 local = True, 185 configure = True, 186 attrs = { 187 "targets": attr.string_list(default = DEFAULT_TARGETS), 188 }, 189) 190