xref: /llvm-project/utils/bazel/configure.bzl (revision 68fdc09eb528ac3b02605333054b4121ce47ec26)
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