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