blob: 3aba341e477b39f69f3c5045486f1d4c0764e4e2 [file] [log] [blame]
Brian Silverman7d89e282021-11-17 17:36:54 -08001# Copyright 2018 The Bazel Authors.
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
15load(
16 "//toolchain/internal:common.bzl",
17 _arch = "arch",
18 _canonical_dir_path = "canonical_dir_path",
19 _check_os_arch_keys = "check_os_arch_keys",
20 _host_tool_features = "host_tool_features",
21 _host_tools = "host_tools",
22 _os = "os",
23 _os_arch_pair = "os_arch_pair",
24 _os_bzl = "os_bzl",
25 _pkg_path_from_label = "pkg_path_from_label",
26 _supported_targets = "SUPPORTED_TARGETS",
27)
28load(
29 "//toolchain/internal:sysroot.bzl",
30 _default_sysroot_path = "default_sysroot_path",
31 _sysroot_path = "sysroot_path",
32)
33
34def _include_dirs_str(rctx, key):
35 dirs = rctx.attr.cxx_builtin_include_directories.get(key)
36 if not dirs:
37 return ""
38 return ("\n" + 12 * " ").join(["\"%s\"," % d for d in dirs])
39
40def llvm_config_impl(rctx):
41 _check_os_arch_keys(rctx.attr.toolchain_roots)
42 _check_os_arch_keys(rctx.attr.sysroot)
43 _check_os_arch_keys(rctx.attr.cxx_builtin_include_directories)
44
45 os = _os(rctx)
46 if os == "windows":
47 rctx.file("BUILD.bazel")
48 rctx.file("toolchains.bzl", """\
49def llvm_register_toolchains():
50 pass
51""")
52 return
53 arch = _arch(rctx)
54
55 key = _os_arch_pair(os, arch)
56 toolchain_root = rctx.attr.toolchain_roots.get(key)
57 if not toolchain_root:
58 toolchain_root = rctx.attr.toolchain_roots.get("")
59 if not toolchain_root:
60 fail("LLVM toolchain root missing for ({}, {})", os, arch)
61
62 # Check if the toolchain root is an absolute path.
63 use_absolute_paths = rctx.attr.absolute_paths
64 if toolchain_root[0] == "/" and (len(toolchain_root) == 1 or toolchain_root[1] != "/"):
65 use_absolute_paths = True
66
67 if use_absolute_paths:
68 llvm_repo_label = Label(toolchain_root + ":BUILD.bazel") # Exact target does not matter.
69 llvm_repo_path = _canonical_dir_path(str(rctx.path(llvm_repo_label).dirname))
70 config_repo_path = _canonical_dir_path(str(rctx.path("")))
71 toolchain_path_prefix = llvm_repo_path
72 tools_path_prefix = llvm_repo_path
73 wrapper_bin_prefix = config_repo_path
74 else:
75 llvm_repo_path = _pkg_path_from_label(Label(toolchain_root + ":BUILD.bazel"))
76 config_repo_path = "external/%s/" % rctx.name
77
78 # tools can only be defined in a subdirectory of config_repo_path,
79 # because their paths are relative to the package defining
80 # cc_toolchain, and cannot contain '..'.
81 # https://github.com/bazelbuild/bazel/issues/7746. To work around
82 # this, we symlink the llvm repo under the package so all tools (except
83 # clang) can be called with normalized relative paths. For clang
84 # however, using a path with symlinks interferes with the header file
85 # inclusion validation checks, because clang frontend will infer the
86 # InstalledDir to be the symlinked path, and will look for header files
87 # in the symlinked path, but that seems to fail the inclusion
88 # validation check. So we always use a cc_wrapper (which is called
89 # through a normalized relative path), and then call clang with the not
90 # symlinked path from the wrapper.
91 rctx.symlink("../../" + llvm_repo_path, "llvm")
92 toolchain_path_prefix = llvm_repo_path
93 tools_path_prefix = "llvm/"
94 wrapper_bin_prefix = ""
95
96 default_sysroot_path = _default_sysroot_path(rctx, os)
97
98 workspace_name = rctx.name
99 toolchain_info = struct(
100 os = os,
101 arch = arch,
102 toolchain_root = toolchain_root,
103 toolchain_path_prefix = toolchain_path_prefix,
104 tools_path_prefix = tools_path_prefix,
105 wrapper_bin_prefix = wrapper_bin_prefix,
106 additional_include_dirs_dict = rctx.attr.cxx_builtin_include_directories,
107 sysroot_dict = rctx.attr.sysroot,
108 default_sysroot_path = default_sysroot_path,
109 llvm_version = rctx.attr.llvm_version,
110 )
111 host_tools_info = dict([
112 pair
113 for (key, tool_path, features) in [
114 # This is used for macOS hosts:
115 ("libtool", "/usr/bin/libtool", [_host_tool_features.SUPPORTS_ARG_FILE]),
116 # This is used with old (pre 7) LLVM versions:
117 ("strip", "/usr/bin/strip", []),
118 # This is used when lld doesn't support the target platform (i.e.
119 # Mach-O for macOS):
120 ("ld", "/usr/bin/ld", []),
121 ]
122 for pair in _host_tools.get_tool_info(rctx, tool_path, features, key).items()
123 ])
124 cc_toolchains_str, toolchain_labels_str = _cc_toolchains_str(
125 workspace_name,
126 toolchain_info,
127 use_absolute_paths,
128 host_tools_info,
129 )
130
131 # Convenience macro to register all generated toolchains.
132 rctx.template(
133 "toolchains.bzl",
134 Label("//toolchain:toolchains.bzl.tpl"),
135 {
136 "%{toolchain_labels}": toolchain_labels_str,
137 },
138 )
139
140 # BUILD file with all the generated toolchain definitions.
141 rctx.template(
142 "BUILD.bazel",
143 Label("//toolchain:BUILD.toolchain.tpl"),
144 {
145 "%{cc_toolchains}": cc_toolchains_str,
146 "%{cc_toolchain_config_bzl}": str(rctx.attr._cc_toolchain_config_bzl),
147 },
148 )
149
150 # CC wrapper script; see comments near the definition of `wrapper_bin_prefix`.
151 if os == "darwin":
152 cc_wrapper_tpl = "//toolchain:osx_cc_wrapper.sh.tpl"
153 else:
154 cc_wrapper_tpl = "//toolchain:cc_wrapper.sh.tpl"
155 rctx.template(
156 "bin/cc_wrapper.sh",
157 Label(cc_wrapper_tpl),
158 {
159 "%{toolchain_path_prefix}": toolchain_path_prefix,
160 },
161 )
162
163 # libtool wrapper; used if the host libtool doesn't support arg files:
164 rctx.template(
165 "bin/host_libtool_wrapper.sh",
166 Label("//toolchain:host_libtool_wrapper.sh.tpl"),
167 {
168 "%{libtool_path}": "/usr/bin/libtool",
169 },
170 )
171
172def _cc_toolchains_str(
173 workspace_name,
174 toolchain_info,
175 use_absolute_paths,
176 host_tools_info):
177 # Since all the toolchains rely on downloading the right LLVM toolchain for
178 # the host architecture, we don't need to explicitly specify
179 # `exec_compatible_with` attribute. If the host and execution platform are
180 # not the same, then host auto-detection based LLVM download does not work
181 # and the user has to explicitly specify the distribution of LLVM they
182 # want.
183
184 # Note that for cross-compiling, the toolchain configuration will need
185 # appropriate sysroots. A recommended approach is to configure two
186 # `llvm_toolchain` repos, one without sysroots (for easy single platform
187 # builds) and register this one, and one with sysroots and provide
188 # `--extra_toolchains` flag when cross-compiling.
189
190 cc_toolchains_str = ""
191 toolchain_names = []
192 for (target_os, target_arch) in _supported_targets:
193 suffix = "{}-{}".format(target_arch, target_os)
194 cc_toolchain_str = _cc_toolchain_str(
195 suffix,
196 target_os,
197 target_arch,
198 toolchain_info,
199 use_absolute_paths,
200 host_tools_info,
201 )
202 if cc_toolchain_str:
203 cc_toolchains_str = cc_toolchains_str + cc_toolchain_str
204 toolchain_name = "@{}//:cc-toolchain-{}".format(workspace_name, suffix)
205 toolchain_names.append(toolchain_name)
206
207 sep = ",\n" + " " * 8 # 2 tabs with tabstop=4.
208 toolchain_labels_str = sep.join(["\"{}\"".format(d) for d in toolchain_names])
209 return cc_toolchains_str, toolchain_labels_str
210
211def _cc_toolchain_str(
212 suffix,
213 target_os,
214 target_arch,
215 toolchain_info,
216 use_absolute_paths,
217 host_tools_info):
218 host_os = toolchain_info.os
219 host_arch = toolchain_info.arch
220
221 host_os_bzl = _os_bzl(host_os)
222 target_os_bzl = _os_bzl(target_os)
223
224 sysroot_path, sysroot = _sysroot_path(
225 toolchain_info.sysroot_dict,
226 target_os,
227 target_arch,
228 )
229 if not sysroot_path:
230 if host_os == target_os and host_arch == target_arch:
231 # For darwin -> darwin, we can use the macOS SDK path.
232 sysroot_path = toolchain_info.default_sysroot_path
233 else:
234 # We are trying to cross-compile without a sysroot, let's bail.
235 # TODO: Are there situations where we can continue?
236 return ""
237
238 extra_files_str = ", \":llvm\", \":wrapper-files\""
239
240 additional_include_dirs = toolchain_info.additional_include_dirs_dict.get(_os_arch_pair(target_os, target_arch))
241 additional_include_dirs_str = "[]"
242 if additional_include_dirs:
243 additional_include_dirs_str = "[{}]".format(
244 ", ".join(["\"{}\"".format(d) for d in additional_include_dirs]),
245 )
246
247 sysroot_label_str = "\"%s\"" % str(sysroot) if sysroot else ""
248
249 # `struct` isn't allowed in `BUILD` files so we JSON encode + decode to turn
250 # them into `dict`s.
251 host_tools_info = json.decode(json.encode(host_tools_info))
252
253 template = """
254# CC toolchain for cc-clang-{suffix}.
255
256cc_toolchain_config(
257 name = "local-{suffix}",
258 host_arch = "{host_arch}",
259 host_os = "{host_os}",
260 target_arch = "{target_arch}",
261 target_os = "{target_os}",
262 toolchain_path_prefix = "{toolchain_path_prefix}",
263 tools_path_prefix = "{tools_path_prefix}",
264 wrapper_bin_prefix = "{wrapper_bin_prefix}",
265 sysroot_path = "{sysroot_path}",
266 additional_include_dirs = {additional_include_dirs_str},
267 llvm_version = "{llvm_version}",
268 host_tools_info = {host_tools_info},
269)
270
271toolchain(
272 name = "cc-toolchain-{suffix}",
273 exec_compatible_with = [
274 "@platforms//cpu:{host_arch}",
275 "@platforms//os:{host_os_bzl}",
276 ],
277 target_compatible_with = [
278 "@platforms//cpu:{target_arch}",
279 "@platforms//os:{target_os_bzl}",
280 ],
281 toolchain = ":cc-clang-{suffix}",
282 toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
283)
284"""
285
286 if use_absolute_paths:
287 template = template + """
288cc_toolchain(
289 name = "cc-clang-{suffix}",
290 all_files = ":empty",
291 compiler_files = ":empty",
292 dwp_files = ":empty",
293 linker_files = ":empty",
294 objcopy_files = ":empty",
295 strip_files = ":empty",
296 toolchain_config = "local-{suffix}",
297)
298"""
299 else:
300 template = template + """
301filegroup(
302 name = "sysroot-components-{suffix}",
303 srcs = [{sysroot_label_str}],
304)
305
306filegroup(
307 name = "compiler-components-{suffix}",
308 srcs = [
309 "{toolchain_root}:clang",
310 "{toolchain_root}:include",
311 ":sysroot-components-{suffix}",
312 ],
313)
314
315filegroup(
316 name = "linker-components-{suffix}",
317 srcs = [
318 "{toolchain_root}:clang",
319 "{toolchain_root}:ld",
320 "{toolchain_root}:ar",
321 "{toolchain_root}:lib",
322 ":sysroot-components-{suffix}",
323 ],
324)
325
326filegroup(
327 name = "all-components-{suffix}",
328 srcs = [
329 "{toolchain_root}:bin",
330 ":compiler-components-{suffix}",
331 ":linker-components-{suffix}",
332 ],
333)
334
335filegroup(name = "all-files-{suffix}", srcs = [":all-components-{suffix}"{extra_files_str}])
336filegroup(name = "archiver-files-{suffix}", srcs = ["{toolchain_root}:ar"{extra_files_str}])
337filegroup(name = "assembler-files-{suffix}", srcs = ["{toolchain_root}:as"{extra_files_str}])
338filegroup(name = "compiler-files-{suffix}", srcs = [":compiler-components-{suffix}"{extra_files_str}])
339filegroup(name = "dwp-files-{suffix}", srcs = ["{toolchain_root}:dwp"{extra_files_str}])
340filegroup(name = "linker-files-{suffix}", srcs = [":linker-components-{suffix}"{extra_files_str}])
341filegroup(name = "objcopy-files-{suffix}", srcs = ["{toolchain_root}:objcopy"{extra_files_str}])
342filegroup(name = "strip-files-{suffix}", srcs = ["{toolchain_root}:strip"{extra_files_str}])
343
344cc_toolchain(
345 name = "cc-clang-{suffix}",
346 all_files = "all-files-{suffix}",
347 ar_files = "archiver-files-{suffix}",
348 as_files = "assembler-files-{suffix}",
349 compiler_files = "compiler-files-{suffix}",
350 dwp_files = "dwp-files-{suffix}",
351 linker_files = "linker-files-{suffix}",
352 objcopy_files = "objcopy-files-{suffix}",
353 strip_files = "strip-files-{suffix}",
354 toolchain_config = "local-{suffix}",
355)
356"""
357
358 return template.format(
359 suffix = suffix,
360 target_os = target_os,
361 target_arch = target_arch,
362 host_os = host_os,
363 host_arch = host_arch,
364 target_os_bzl = target_os_bzl,
365 host_os_bzl = host_os_bzl,
366 toolchain_root = toolchain_info.toolchain_root,
367 toolchain_path_prefix = toolchain_info.toolchain_path_prefix,
368 tools_path_prefix = toolchain_info.tools_path_prefix,
369 wrapper_bin_prefix = toolchain_info.wrapper_bin_prefix,
370 additional_include_dirs_str = additional_include_dirs_str,
371 sysroot_label_str = sysroot_label_str,
372 sysroot_path = sysroot_path,
373 llvm_version = toolchain_info.llvm_version,
374 extra_files_str = extra_files_str,
375 host_tools_info = host_tools_info,
376 )