• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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