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"""
16This file contains rules for defininig GBL toolchains.
17"""
18
19load(
20    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
21    "action_config",
22    "flag_group",
23    "flag_set",
24    "tool",
25    "tool_path",
26)
27load("@gbl_llvm_prebuilts//:info.bzl", "LLVM_PREBUILTS_C_INCLUDE", "gbl_llvm_tool_path")
28load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "ALL_CPP_COMPILE_ACTION_NAMES")
29
30def _flag_set(flags):
31    """Convert a list of compile/link flags to a flag_set type."""
32    return flag_set(
33        flag_groups = [
34            flag_group(
35                flags = flags,
36            ),
37        ],
38    )
39
40def _gbl_clang_cc_toolchain_config_impl(ctx):
41    """Implementation for rule _gbl_clang_cc_toolchain_config()"""
42    builtin_includes = ctx.attr.builtin_includes or [LLVM_PREBUILTS_C_INCLUDE]
43    common_compile_flagset = [
44        _flag_set([
45            "--target={}".format(ctx.attr.target_system_triple),
46            "-nostdinc",
47            "-no-canonical-prefixes",
48            "-Werror",
49            "-Wall",
50        ] + ["-I{}".format(inc) for inc in builtin_includes] + ctx.attr.cc_flags),
51    ]
52    return cc_common.create_cc_toolchain_config_info(
53        ctx = ctx,
54        cxx_builtin_include_directories = builtin_includes,
55        toolchain_identifier = "{}_id".format(ctx.attr.cc_toolchain_name),
56        host_system_name = "local",
57        target_system_name = ctx.attr.target_system_triple,
58        target_cpu = ctx.attr.target_cpu,
59        target_libc = "unknown",
60        compiler = "clang",
61        abi_version = "unknown",
62        abi_libc_version = "unknown",
63        action_configs = [
64            action_config(
65                action_name = action_name,
66                enabled = True,
67                tools = [tool(path = gbl_llvm_tool_path("clang++"))],
68                flag_sets = common_compile_flagset,
69            )
70            for action_name in ALL_CPP_COMPILE_ACTION_NAMES +
71                               [ACTION_NAMES.assemble, ACTION_NAMES.preprocess_assemble]
72        ] + [
73            action_config(
74                action_name = ACTION_NAMES.c_compile,
75                enabled = True,
76                tools = [tool(path = gbl_llvm_tool_path("clang"))],
77                flag_sets = common_compile_flagset,
78            ),
79        ] + [
80            action_config(
81                action_name = ACTION_NAMES.cpp_link_executable,
82                enabled = True,
83                tools = [tool(path = gbl_llvm_tool_path("clang++"))],
84                flag_sets = [_flag_set(ctx.attr.ld_flags)] if ctx.attr.ld_flags else [],
85            ),
86        ],
87        tool_paths = [
88            tool_path(
89                name = "ld",
90                path = gbl_llvm_tool_path("clang++"),
91            ),
92            tool_path(
93                name = "ar",
94                path = gbl_llvm_tool_path("llvm-ar"),
95            ),
96            tool_path(
97                name = "cpp",
98                path = gbl_llvm_tool_path("clang++"),
99            ),
100            tool_path(
101                name = "gcc",
102                path = gbl_llvm_tool_path("clang"),
103            ),
104            tool_path(
105                name = "gcov",
106                path = gbl_llvm_tool_path("llvm-cov"),
107            ),
108            tool_path(
109                name = "nm",
110                path = gbl_llvm_tool_path("llvm-nm"),
111            ),
112            tool_path(
113                name = "objdump",
114                path = gbl_llvm_tool_path("llvm-objdump"),
115            ),
116            tool_path(
117                name = "strip",
118                path = gbl_llvm_tool_path("llvm-strip"),
119            ),
120        ],
121    )
122
123_gbl_clang_cc_toolchain_config = rule(
124    implementation = _gbl_clang_cc_toolchain_config_impl,
125    attrs = {
126        "cc_toolchain_name": attr.string(),
127        "target_cpu": attr.string(),
128        "target_system_triple": attr.string(),
129        "cc_flags": attr.string_list(),
130        "ld_flags": attr.string_list(),
131        "builtin_includes": attr.string_list(),
132    },
133    provides = [CcToolchainConfigInfo],
134)
135
136def gbl_clang_cc_toolchain(
137        name,
138        target_cpu,
139        target_system_triple,
140        cc_flags = None,
141        ld_flags = None,
142        builtin_includes = None):
143    """Configure a clang based cc_toolchain().
144
145    Args:
146        name (String): Name of the target.
147        target_cpu (string): Target CPU.
148        target_system_triple (string): LLVM-style target system triple.
149        cc_flags (List): clang compile flags.
150        ld_flags (List): clang link flags
151        builtin_includes (List): Override the default list of builtin include dirs.
152    """
153    config_name = "_{}_config".format(name)
154    _gbl_clang_cc_toolchain_config(
155        name = config_name,
156        cc_toolchain_name = name,
157        target_cpu = target_cpu,
158        target_system_triple = target_system_triple,
159        cc_flags = cc_flags,
160        ld_flags = ld_flags,
161        builtin_includes = builtin_includes,
162    )
163
164    empty_filegroup = "_empty_{}".format(name)
165    native.filegroup(name = empty_filegroup)
166    empty_filegroup_target = ":{}".format(empty_filegroup)
167
168    native.cc_toolchain(
169        name = name,
170        toolchain_identifier = name,
171        toolchain_config = ":{}".format(config_name),
172        all_files = empty_filegroup_target,
173        compiler_files = empty_filegroup_target,
174        dwp_files = empty_filegroup_target,
175        linker_files = empty_filegroup_target,
176        objcopy_files = empty_filegroup_target,
177        strip_files = empty_filegroup_target,
178        supports_param_files = 0,
179    )
180
181def _prebuilt_binary_impl(ctx):
182    """Generate a wrapper executable type target that simply symlinks to a given executable binary.
183
184    This is for rules that only accept executable type target but not binary file directly.
185    i.e. `rust_bindgen_toolchain`
186    """
187    out = ctx.actions.declare_file(ctx.label.name)
188    ctx.actions.symlink(
189        output = out,
190        target_file = ctx.executable.bin,
191    )
192    return [DefaultInfo(files = depset([out]), executable = out)]
193
194prebuilt_binary = rule(
195    implementation = _prebuilt_binary_impl,
196    executable = True,
197    attrs = {
198        "bin": attr.label(allow_single_file = True, executable = True, cfg = "exec"),
199    },
200)
201
202# A transition rule that emits the `@gbl//toolchain:rust_no_sysroot_true` setting.
203def _no_sysroot_transition_impl(_, __):
204    return {"@gbl//toolchain:rust_no_sysroot": True}
205
206_no_sysroot_transition = transition(
207    implementation = _no_sysroot_transition_impl,
208    inputs = [],
209    outputs = ["@gbl//toolchain:rust_no_sysroot"],
210)
211
212# A rule implementation that simply forwards dependencies from attribute `deps` and generates
213# symlinks to their output files.
214def _forward_and_symlink(ctx):
215    outs = []
216    for file in ctx.files.deps:
217        # Append the label name to the file name but keep the same extension. i.e.
218        # "<file>.<extension>" -> "<file>_<label>.<extension>"
219        stem = file.basename.removesuffix(".{}".format(file.extension))
220        out = ctx.actions.declare_file("{}_{}.{}".format(stem, ctx.label.name, file.extension))
221        ctx.actions.symlink(output = out, target_file = file)
222        outs.append(out)
223    return [DefaultInfo(files = depset(outs))]
224
225# A rule for building rust targets with the `@gbl//toolchain:rust_no_sysroot_true` setting.
226build_with_no_rust_sysroot = rule(
227    implementation = _forward_and_symlink,
228    cfg = _no_sysroot_transition,
229    attrs = {
230        # Mandatory attribute for rules with transition.
231        "_allowlist_function_transition": attr.label(
232            default = Label("@bazel_tools//tools/allowlists/function_transition_allowlist"),
233        ),
234        "deps": attr.label_list(allow_files = True, mandatory = True),
235    },
236)
237
238# A transition rule that emits the "--platforms=<attr.platform>" option.
239def _platform_transition_impl(_, attr):
240    return {"//command_line_option:platforms": attr.platform}
241
242_platform_transition = transition(
243    implementation = _platform_transition_impl,
244    inputs = [],
245    outputs = ["//command_line_option:platforms"],
246)
247
248build_with_platform = rule(
249    implementation = _forward_and_symlink,
250    cfg = _platform_transition,
251    attrs = {
252        # Mandatory attribute for rules with transition.
253        "_allowlist_function_transition": attr.label(
254            default = Label("@bazel_tools//tools/allowlists/function_transition_allowlist"),
255        ),
256        "platform": attr.string(mandatory = True),
257        "deps": attr.label_list(allow_files = True, mandatory = True),
258    },
259)
260
261# This rule creates symlink for a static library in both Linux/GNU and MSVC naming styles so that
262# rust linker is able to find it when building for them.
263#
264# When flag "-Clink-arg=-l<libname>" is passed to rustc, for Linux/GNU target platforms, the linker
265# looks for library named "lib<libname>.a", for MSVC target plaforms (i.e. UEFI), the linker looks
266# for library named "<libname>.lib". When bazel builds a cc_library target, it always outputs the
267# Linux/GNU naming style and therefore fails linking when building for UEFI targets.
268def _link_static_cc_library_impl(ctx):
269    # Put an underscore so that we don't need to deal with potential "lib" prefix from user
270    # provided name.
271    libname = "_{}".format(ctx.label.name)
272
273    # Create symlink for both naming styles.
274    out_msvc_style = ctx.actions.declare_file("{}.lib".format(libname))
275    ctx.actions.symlink(output = out_msvc_style, target_file = ctx.files.cc_library[0])
276    out_linux_style = ctx.actions.declare_file("lib{}.a".format(libname))
277    ctx.actions.symlink(output = out_linux_style, target_file = ctx.files.cc_library[0])
278
279    # Construct and return a `CcInfo` for this rule that includes the library to link, so that
280    # other rust_library/cc_library can depend directly on this target.
281    library_to_link = cc_common.create_library_to_link(
282        actions = ctx.actions,
283        # Either is fine, since both yield the same linker option by Bazel.
284        static_library = out_linux_style,
285    )
286    linking_input = cc_common.create_linker_input(
287        owner = ctx.label,
288        libraries = depset([library_to_link]),
289        # Make sure both symlinks are generated.
290        additional_inputs = depset([out_msvc_style, out_linux_style]),
291    )
292    linking_context = cc_common.create_linking_context(linker_inputs = depset([linking_input]))
293    return [CcInfo(linking_context = linking_context)]
294
295link_static_cc_library = rule(
296    implementation = _link_static_cc_library_impl,
297    attrs = {
298        "cc_library": attr.label(),  # The cc_library() target for the static library.
299    },
300)
301