1# Copyright (C) 2023 The Android Open Source Project
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#       http://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"""Defines a repository that provides all clang toolchains."""
16
17# General comment on toolchain registration orders:
18#
19# "When using target patterns to register toolchains, the order in which
20# the individual toolchains are registered is determined by the following rules:
21# [...]
22# Within a package, toolchains are registered in the lexicographical order of their names."
23#
24# Toolchains in this repository is prefixed with numbers to show their ordering.
25#
26# See
27# https://bazel.build/extending/toolchains#registering-building-toolchains
28
29def _clang_toolchain_repository_impl(repository_ctx):
30    repository_ctx.file("WORKSPACE.bazel", """\
31workspace(name = "{}")
32""".format(repository_ctx.attr.name))
33
34    build_file_content = '''\
35"""
36All clang toolchains used by Kleaf.
37"""
38load("@kernel_toolchain_info//:dict.bzl","VARS")
39load("{architecture_constants}", "SUPPORTED_ARCHITECTURES")
40load("{clang_toolchain}", "clang_toolchain")
41load("{versions}", "VERSIONS")
42'''.format(
43        architecture_constants = Label(":architecture_constants.bzl"),
44        clang_toolchain = Label(":clang_toolchain.bzl"),
45        versions = Label(":versions.bzl"),
46    )
47
48    if "KLEAF_USER_CLANG_TOOLCHAIN_PATH" not in repository_ctx.os.environ:
49        build_file_content += _empty_clang_toolchain_build_file()
50    else:
51        build_file_content += _real_clang_toolchain_build_file(repository_ctx)
52
53    build_file_content += _common_aliases_build_file()
54
55    repository_ctx.file("BUILD.bazel", build_file_content)
56
57def _empty_clang_toolchain_build_file():
58    build_file_content = '''\
59
60load("{empty_toolchain}", "empty_toolchain")
61
62toolchain_type(
63    name = "empty_toolchain_type",
64    visibility = ["//visibility:private"],
65)
66
67[empty_toolchain(
68    name = "1_user_{{}}_clang_toolchain".format(arch.name),
69    toolchain_type = ":empty_toolchain_type",
70    visibility = ["//visibility:private"],
71) for arch in SUPPORTED_ARCHITECTURES]
72'''.format(
73        empty_toolchain = Label(":empty_toolchain.bzl"),
74    )
75    return build_file_content
76
77def _real_clang_toolchain_build_file(repository_ctx):
78    user_clang_toolchain_path = repository_ctx.os.environ["KLEAF_USER_CLANG_TOOLCHAIN_PATH"]
79    user_clang_toolchain_path = repository_ctx.path(user_clang_toolchain_path)
80
81    # Symlink contents of user_clang_toolchain_path to the top of the repository
82    for subpath in user_clang_toolchain_path.readdir():
83        if subpath.basename in ("BUILD.bazel", "BUILD", "WORKSPACE.bazel", "WORKSPACE"):
84            continue
85
86        subpath_s = str(subpath)
87        user_clang_toolchain_path_s = str(user_clang_toolchain_path)
88        if not subpath_s.startswith(user_clang_toolchain_path_s + "/"):
89            fail("FATAL: {} does not start with {}/".format(
90                subpath_s,
91                user_clang_toolchain_path_s,
92            ))
93
94        repository_ctx.symlink(
95            subpath,
96            subpath_s.removeprefix(user_clang_toolchain_path_s + "/"),
97        )
98
99    build_file_content = '''\
100
101package(default_visibility = ["//visibility:public"])
102
103filegroup(
104    name = "binaries",
105    srcs = glob([
106        "bin/*",
107        "lib/*",
108        "lib/x86_64-unknown-linux-gnu/*",
109    ]),
110)
111
112filegroup(
113    name = "includes",
114    srcs = glob([
115        "lib/clang/*/include/**",
116        "include/c++/**",
117        "include/x86_64-unknown-linux-gnu/c++/**",
118    ]),
119)
120
121[clang_toolchain(
122    name = "1_user_{{}}_clang_toolchain".format(arch.name),
123    arch = arch,
124    clang_pkg = ":fake_anchor_target",
125    clang_version = "kleaf_user_clang_toolchain_skip_version_check",
126) for arch in SUPPORTED_ARCHITECTURES]
127'''.format(
128        architecture_constants = Label(":architecture_constants.bzl"),
129        clang_toolchain = Label(":clang_toolchain.bzl"),
130        versions = Label(":versions.bzl"),
131    )
132
133    return build_file_content
134
135def _common_aliases_build_file():
136    # Label(): Resolve the label against this extension (clang_toolchain_repository.bzl) so the
137    # workspace name is injected properly when //prebuilts is in a subworkspace.
138    # @<kleaf tooling workspace>//prebuilts/clang/host/linux-x86/kleaf
139    this_pkg = str(Label(":x")).removesuffix(":x")
140
141    # @<kleaf tooling workspace>//prebuilts/clang/host/linux-x86
142    linux_x86_pkg = this_pkg.removesuffix("/kleaf")
143
144    build_file_content = """
145
146# Default toolchains.
147
148[clang_toolchain(
149    name = "2_versioned_{{}}_{{}}_clang_toolchain".format(version, arch.name),
150    clang_pkg = "{linux_x86_pkg}/clang-{{}}".format(version),
151    clang_version = version,
152    extra_compatible_with = ["{this_pkg}:{{}}".format(version)],
153    arch = arch,
154) for version in VERSIONS for arch in SUPPORTED_ARCHITECTURES]
155
156[clang_toolchain(
157    name = "3_default_{{}}_clang_toolchain".format(arch.name),
158    clang_pkg = "{linux_x86_pkg}/clang-{{}}".format(VARS["CLANG_VERSION"]),
159    clang_version = VARS["CLANG_VERSION"],
160    arch = arch,
161) for arch in SUPPORTED_ARCHITECTURES]
162
163""".format(
164        this_pkg = this_pkg,
165        linux_x86_pkg = linux_x86_pkg,
166    )
167
168    return build_file_content
169
170clang_toolchain_repository = repository_rule(
171    doc = """Defines a repository that provides all clang toolchains Kleaf uses.
172
173    Register them as follows:
174
175    ```
176    register_toolchains("@kleaf_clang_toolchain//:all")
177    ```
178
179    The user clang toolchain is expected from the path defined in the
180    `KLEAF_USER_CLANG_TOOLCHAIN_PATH` environment variable, if set.
181""",
182    implementation = _clang_toolchain_repository_impl,
183    environ = [
184        "KLEAF_USER_CLANG_TOOLCHAIN_PATH",
185    ],
186    local = True,
187)
188