• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""
2This file specifies a clang toolchain that can run on a Mac host (with either M1 or Intel CPU).
3
4Hermetic toolchains still need access to Xcode for sys headers included in Skia's codebase.
5
6See download_mac_toolchain.bzl for more details on the creation of the toolchain.
7
8It uses the usr subfolder of the built toolchain as a sysroot
9
10It follows the example of:
11 - linux_amd64_toolchain_config.bzl
12"""
13
14# https://github.com/bazelbuild/bazel/blob/master/tools/cpp/cc_toolchain_config_lib.bzl
15load(
16    "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl",
17    "action_config",
18    "feature",
19    "flag_group",
20    "flag_set",
21    "tool",
22    "variable_with_value",
23)
24
25# https://github.com/bazelbuild/bazel/blob/master/tools/build_defs/cc/action_names.bzl
26load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
27load(":clang_layering_check.bzl", "make_layering_check_features")
28
29# The location of the created clang toolchain.
30EXTERNAL_TOOLCHAIN = "external/clang_mac"
31
32# Root of our symlinks. These symlinks are created in download_mac_toolchain.bzl
33XCODE_MACSDK_SYMLINK = EXTERNAL_TOOLCHAIN + "/symlinks/xcode/MacSDK"
34
35_platform_constraints_to_import = {
36    "@platforms//cpu:arm64": "_arm64_cpu",
37    "@platforms//cpu:x86_64": "_x86_64_cpu",
38}
39
40def _mac_toolchain_info(ctx):
41    action_configs = _make_action_configs()
42    features = []
43    features += _make_default_flags()
44    features += make_layering_check_features()
45    features += _make_diagnostic_flags()
46    features += _make_target_specific_flags(ctx)
47
48    # https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info
49    # Note, this rule is defined in Java code, not Starlark
50    # https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CcModuleApi.java
51    return cc_common.create_cc_toolchain_config_info(
52        ctx = ctx,
53        features = features,
54        action_configs = action_configs,
55        builtin_sysroot = EXTERNAL_TOOLCHAIN,
56        cxx_builtin_include_directories = [
57            # https://stackoverflow.com/a/61419490
58            # "If the compiler has --sysroot support, then these paths should use %sysroot%
59            #  rather than the include path"
60            # https://bazel.build/rules/lib/cc_common#create_cc_toolchain_config_info.cxx_builtin_include_directories
61            "%sysroot%/symlinks/xcode/MacSDK/Frameworks/",
62        ],
63        # These are required, but do nothing
64        compiler = "",
65        target_cpu = "",
66        target_libc = "",
67        target_system_name = "",
68        toolchain_identifier = "",
69    )
70
71def _import_platform_constraints():
72    # In order to "import" constraint values so they can be passed in as parameters to
73    # ctx.target_platform_has_constraint(), we need to list them as a default value on a
74    # private attributes. It doesn't really matter what we call these private attributes,
75    # but to make it easier to read elsewhere, we create a mapping between the "official"
76    # name of the constraints and the private name. Then, we can refer to the official name
77    # without having to remember the secondary name.
78    # https://bazel.build/rules/rules#private_attributes_and_implicit_dependencies
79    # https://github.com/bazelbuild/proposals/blob/91579f36031f768bcf68b18a86b8df8b43cc590b/designs/2019-11-11-target-platform-constraints.md
80    rule_attributes = {}
81    for constraint in _platform_constraints_to_import:
82        private_attr = _platform_constraints_to_import[constraint]
83        rule_attributes[private_attr] = attr.label(default = constraint)
84    return rule_attributes
85
86def _has_platform_constraint(ctx, official_constraint_name):
87    # ctx is of type https://bazel.build/rules/lib/ctx
88    # This pattern is from
89    # https://github.com/bazelbuild/proposals/blob/91579f36031f768bcf68b18a86b8df8b43cc590b/designs/2019-11-11-target-platform-constraints.md
90    private_attr = _platform_constraints_to_import[official_constraint_name]
91    constraint = getattr(ctx.attr, private_attr)[platform_common.ConstraintValueInfo]
92    return ctx.target_platform_has_constraint(constraint)
93
94provide_mac_toolchain_config = rule(
95    attrs = _import_platform_constraints(),
96    provides = [CcToolchainConfigInfo],
97    implementation = _mac_toolchain_info,
98)
99
100def _make_action_configs():
101    """
102    This function sets up the tools needed to perform the various compile/link actions.
103
104    Bazel normally restricts us to referring to (and therefore running) executables/scripts
105    that are in this directory (That is EXEC_ROOT/toolchain). However, the executables we want
106    to run are brought in via WORKSPACE.bazel and are located in EXEC_ROOT/external/clang....
107    Therefore, we make use of "trampoline scripts" that will call the binaries from the
108    toolchain directory.
109
110    These action_configs also let us dynamically specify arguments from the Bazel
111    environment if necessary (see cpp_link_static_library_action).
112    """
113
114    # https://cs.opensource.google/bazel/bazel/+/master:tools/cpp/cc_toolchain_config_lib.bzl;l=435;drc=3b9e6f201a9a3465720aad8712ab7bcdeaf2e5da
115    clang_tool = tool(path = "mac_trampolines/clang_trampoline_mac.sh")
116    lld_tool = tool(path = "mac_trampolines/lld_trampoline_mac.sh")
117    ar_tool = tool(path = "mac_trampolines/ar_trampoline_mac.sh")
118
119    # https://cs.opensource.google/bazel/bazel/+/master:tools/cpp/cc_toolchain_config_lib.bzl;l=488;drc=3b9e6f201a9a3465720aad8712ab7bcdeaf2e5da
120    assemble_action = action_config(
121        action_name = ACTION_NAMES.assemble,
122        tools = [clang_tool],
123    )
124    c_compile_action = action_config(
125        action_name = ACTION_NAMES.c_compile,
126        tools = [clang_tool],
127    )
128    cpp_compile_action = action_config(
129        action_name = ACTION_NAMES.cpp_compile,
130        tools = [clang_tool],
131    )
132    objc_compile_action = action_config(
133        action_name = ACTION_NAMES.objc_compile,
134        tools = [clang_tool],
135    )
136    objcpp_compile_action = action_config(
137        action_name = ACTION_NAMES.objcpp_compile,
138        tools = [clang_tool],
139    )
140    linkstamp_compile_action = action_config(
141        action_name = ACTION_NAMES.linkstamp_compile,
142        tools = [clang_tool],
143    )
144    preprocess_assemble_action = action_config(
145        action_name = ACTION_NAMES.preprocess_assemble,
146        tools = [clang_tool],
147    )
148
149    cpp_link_dynamic_library_action = action_config(
150        action_name = ACTION_NAMES.cpp_link_dynamic_library,
151        tools = [lld_tool],
152    )
153    cpp_link_executable_action = action_config(
154        action_name = ACTION_NAMES.cpp_link_executable,
155        # Bazel assumes it is talking to clang when building an executable. There are
156        # "-Wl" flags on the command: https://releases.llvm.org/6.0.1/tools/clang/docs/ClangCommandLineReference.html#cmdoption-clang-Wl
157        tools = [clang_tool],
158    )
159    cpp_link_nodeps_dynamic_library_action = action_config(
160        action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
161        tools = [lld_tool],
162    )
163
164    # objc archiver and cpp archiver actions use the same base flags
165    common_archive_flags = [
166        flag_set(
167            flag_groups = [
168                flag_group(
169                    # https://llvm.org/docs/CommandGuide/llvm-ar.html
170                    # [r]eplace existing files or insert them if they already exist,
171                    # [c]reate the file if it doesn't already exist
172                    # [s]ymbol table should be added
173                    # [D]eterministic timestamps should be used
174                    flags = ["rcsD", "%{output_execpath}"],
175                    # Despite the name, output_execpath just refers to linker output,
176                    # e.g. libFoo.a
177                    expand_if_available = "output_execpath",
178                ),
179            ],
180        ),
181        flag_set(
182            flag_groups = [
183                flag_group(
184                    iterate_over = "libraries_to_link",
185                    flag_groups = [
186                        flag_group(
187                            flags = ["%{libraries_to_link.name}"],
188                            expand_if_equal = variable_with_value(
189                                name = "libraries_to_link.type",
190                                value = "object_file",
191                            ),
192                        ),
193                        flag_group(
194                            flags = ["%{libraries_to_link.object_files}"],
195                            iterate_over = "libraries_to_link.object_files",
196                            expand_if_equal = variable_with_value(
197                                name = "libraries_to_link.type",
198                                value = "object_file_group",
199                            ),
200                        ),
201                    ],
202                    expand_if_available = "libraries_to_link",
203                ),
204            ],
205        ),
206        flag_set(
207            flag_groups = [
208                flag_group(
209                    flags = ["@%{linker_param_file}"],
210                    expand_if_available = "linker_param_file",
211                ),
212            ],
213        ),
214    ]
215
216    # This is the same rule as
217    # https://github.com/emscripten-core/emsdk/blob/7f39d100d8cd207094decea907121df72065517e/bazel/emscripten_toolchain/crosstool.bzl#L143
218    # By default, there are no flags or libraries passed to the llvm-ar tool, so
219    # we need to specify them. The variables mentioned by expand_if_available are defined
220    # https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables
221    cpp_link_static_library_action = action_config(
222        action_name = ACTION_NAMES.cpp_link_static_library,
223        flag_sets = common_archive_flags,
224        tools = [ar_tool],
225    )
226
227    objc_archive_action = action_config(
228        action_name = ACTION_NAMES.objc_archive,
229        flag_sets = common_archive_flags,
230        tools = [ar_tool],
231    )
232
233    action_configs = [
234        assemble_action,
235        c_compile_action,
236        cpp_compile_action,
237        cpp_link_dynamic_library_action,
238        cpp_link_executable_action,
239        cpp_link_nodeps_dynamic_library_action,
240        cpp_link_static_library_action,
241        linkstamp_compile_action,
242        objc_archive_action,
243        objc_compile_action,
244        objcpp_compile_action,
245        preprocess_assemble_action,
246    ]
247    return action_configs
248
249# In addition to pointing the c and cpp compile actions to our toolchain, we also need to set objc
250# and objcpp action flags as well. We build .m and .mm files with the objc_library rule, which
251# will use the default toolchain if not specified here.
252# https://docs.bazel.build/versions/3.3.0/be/objective-c.html#objc_library
253#
254# Note: These values must be kept in sync with those defined in cmake_exporter.go.
255def _make_default_flags():
256    """Here we define the flags for certain actions that are always applied.
257
258    For any flag that might be conditionally applied, it should be defined in //bazel/copts.bzl.
259
260    Flags that are set here will be unconditionally applied to everything we compile with
261    this toolchain, even third_party deps.
262
263    """
264    cxx_compile_includes = flag_set(
265        actions = [
266            ACTION_NAMES.c_compile,
267            ACTION_NAMES.cpp_compile,
268            ACTION_NAMES.objc_compile,
269            ACTION_NAMES.objcpp_compile,
270        ],
271        flag_groups = [
272            flag_group(
273                flags = [
274                    # THIS ORDER MATTERS GREATLY. If these are in the wrong order, the
275                    # #include_next directives will fail to find the files, causing a compilation
276                    # error (or, without -no-canonical-prefixes, a mysterious case where files
277                    # are included with an absolute path and fail the build).
278                    "-isystem",
279                    EXTERNAL_TOOLCHAIN + "/include/c++/v1",
280                    "-isystem",
281                    XCODE_MACSDK_SYMLINK + "/usr/include",
282                    "-isystem",
283                    EXTERNAL_TOOLCHAIN + "/lib/clang/15.0.1/include",
284                    # Set the framework path to the Mac SDK framework directory. This has
285                    # subfolders like OpenGL.framework
286                    # We want -iframework so Clang hides diagnostic warnings from those header
287                    # files we include. -F does not hide those.
288                    "-iframework",
289                    XCODE_MACSDK_SYMLINK + "/Frameworks",
290                    # We do not want clang to search in absolute paths for files. This makes
291                    # Bazel think we are using an outside resource and fail the compile.
292                    "-no-canonical-prefixes",
293                ],
294            ),
295        ],
296    )
297
298    cpp_compile_flags = flag_set(
299        actions = [
300            ACTION_NAMES.cpp_compile,
301            ACTION_NAMES.objc_compile,
302            ACTION_NAMES.objcpp_compile,
303        ],
304        flag_groups = [
305            flag_group(
306                flags = [
307                    "-std=c++17",
308                ],
309            ),
310        ],
311    )
312
313    # copts and defines appear to not automatically be set
314    # https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables
315    # https://github.com/bazelbuild/bazel/blob/5ad4a6126be2bdc53ee7e2457e076c90efe86d56/tools/cpp/cc_toolchain_config_lib.bzl#L200-L209
316    objc_compile_flags = flag_set(
317        actions = [
318            ACTION_NAMES.objc_compile,
319            ACTION_NAMES.objcpp_compile,
320        ],
321        flag_groups = [
322            flag_group(
323                iterate_over = "user_compile_flags",
324                flags = ["%{user_compile_flags}"],
325            ),
326            flag_group(
327                iterate_over = "preprocessor_defines",
328                flags = ["-D%{preprocessor_defines}"],
329            ),
330        ],
331    )
332
333    link_exe_flags = flag_set(
334        actions = [ACTION_NAMES.cpp_link_executable],
335        flag_groups = [
336            flag_group(
337                flags = [
338                    # lld goes through dynamic library dependencies for dylib and tbh files through
339                    # absolute paths (/System/Library/Frameworks). However, the dependencies live in
340                    # [Xcode dir]/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks
341                    # -Wl tells clang to forward the next flag to the linker.
342                    # -syslibroot appends to the beginning of the dylib dependency path.
343                    # https://github.com/llvm/llvm-project/blob/d61341768cf0cff7ceeaddecc2f769b5c1b901c4/lld/MachO/InputFiles.cpp#L1418-L1420
344                    "-Wl,-syslibroot",
345                    XCODE_MACSDK_SYMLINK,
346                    "-fuse-ld=lld",
347                    # We chose to use the llvm runtime, not the gcc one because it is already
348                    # included in the clang binary
349                    "--rtlib=compiler-rt",
350                    "-std=c++17",
351                    "-lstdc++",
352                ],
353            ),
354        ],
355    )
356
357    return [feature(
358        "default_flags",
359        enabled = True,
360        flag_sets = [
361            cpp_compile_flags,
362            cxx_compile_includes,
363            link_exe_flags,
364            objc_compile_flags,
365        ],
366    )]
367
368def _make_diagnostic_flags():
369    """Here we define the flags that can be turned on via features to yield debug info."""
370    cxx_diagnostic = flag_set(
371        actions = [
372            ACTION_NAMES.c_compile,
373            ACTION_NAMES.cpp_compile,
374        ],
375        flag_groups = [
376            flag_group(
377                flags = [
378                    "--trace-includes",
379                    "-v",
380                ],
381            ),
382        ],
383    )
384
385    link_diagnostic = flag_set(
386        actions = [ACTION_NAMES.cpp_link_executable],
387        flag_groups = [
388            flag_group(
389                flags = [
390                    "-Wl,--verbose",
391                    "-v",
392                ],
393            ),
394        ],
395    )
396
397    link_search_dirs = flag_set(
398        actions = [ACTION_NAMES.cpp_link_executable],
399        flag_groups = [
400            flag_group(
401                flags = [
402                    "--print-search-dirs",
403                ],
404            ),
405        ],
406    )
407    return [
408        # Running a Bazel command with --features diagnostic will cause the compilation and
409        # link steps to be more verbose.
410        feature(
411            "diagnostic",
412            enabled = False,
413            flag_sets = [
414                cxx_diagnostic,
415                link_diagnostic,
416            ],
417        ),
418        # Running a Bazel command with --features print_search_dirs will cause the link to fail
419        # but directories searched for libraries, etc will be displayed.
420        feature(
421            "print_search_dirs",
422            enabled = False,
423            flag_sets = [
424                link_search_dirs,
425            ],
426        ),
427    ]
428
429# The parameter is of type https://bazel.build/rules/lib/ctx
430def _make_target_specific_flags(ctx):
431    m1_mac_target = flag_set(
432        actions = [
433            ACTION_NAMES.assemble,
434            ACTION_NAMES.preprocess_assemble,
435            ACTION_NAMES.c_compile,
436            ACTION_NAMES.cpp_compile,
437            ACTION_NAMES.objc_compile,
438            ACTION_NAMES.objcpp_compile,
439            ACTION_NAMES.cpp_link_executable,
440            ACTION_NAMES.cpp_link_dynamic_library,
441        ],
442        flag_groups = [
443            flag_group(
444                flags = [
445                    "--target=arm64-apple-macos11",
446                ],
447            ),
448        ],
449    )
450    intel_mac_target = flag_set(
451        actions = [
452            ACTION_NAMES.assemble,
453            ACTION_NAMES.preprocess_assemble,
454            ACTION_NAMES.c_compile,
455            ACTION_NAMES.cpp_compile,
456            ACTION_NAMES.objc_compile,
457            ACTION_NAMES.objcpp_compile,
458            ACTION_NAMES.cpp_link_executable,
459            ACTION_NAMES.cpp_link_dynamic_library,
460        ],
461        flag_groups = [
462            flag_group(
463                flags = [
464                    "--target=x86_64-apple-macos11",
465                ],
466            ),
467        ],
468    )
469
470    target_specific_features = []
471    if _has_platform_constraint(ctx, "@platforms//cpu:arm64"):
472        target_specific_features.append(
473            feature(
474                name = "_m1_mac_target",
475                enabled = True,
476                flag_sets = [m1_mac_target],
477            ),
478        )
479    elif _has_platform_constraint(ctx, "@platforms//cpu:x86_64"):
480        target_specific_features.append(
481            feature(
482                name = "_intel_mac_target",
483                enabled = True,
484                flag_sets = [intel_mac_target],
485            ),
486        )
487
488    return target_specific_features
489