xref: /aosp_15_r20/external/skia/toolchain/mac_toolchain_config.bzl (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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()
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():
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    cxx_compile_includes = flag_set(
266        actions = [
267            ACTION_NAMES.c_compile,
268            ACTION_NAMES.cpp_compile,
269            ACTION_NAMES.objc_compile,
270            ACTION_NAMES.objcpp_compile,
271        ],
272        flag_groups = [
273            flag_group(
274                flags = [
275                    # THIS ORDER MATTERS GREATLY. If these are in the wrong order, the
276                    # #include_next directives will fail to find the files, causing a compilation
277                    # error (or, without -no-canonical-prefixes, a mysterious case where files
278                    # are included with an absolute path and fail the build).
279                    "-isystem",
280                    EXTERNAL_TOOLCHAIN + "/include/c++/v1",
281                    "-isystem",
282                    XCODE_MACSDK_SYMLINK + "/usr/include",
283                    "-isystem",
284                    EXTERNAL_TOOLCHAIN + "/lib/clang/15.0.1/include",
285                    # Set the framework path to the Mac SDK framework directory. This has
286                    # subfolders like OpenGL.framework
287                    # We want -iframework so Clang hides diagnostic warnings from those header
288                    # files we include. -F does not hide those.
289                    "-iframework",
290                    XCODE_MACSDK_SYMLINK + "/System/Library/Frameworks",
291                    # We do not want clang to search in absolute paths for files. This makes
292                    # Bazel think we are using an outside resource and fail the compile.
293                    "-no-canonical-prefixes",
294                ],
295            ),
296        ],
297    )
298
299    cpp_compile_flags = flag_set(
300        actions = [
301            ACTION_NAMES.cpp_compile,
302            ACTION_NAMES.objc_compile,
303            ACTION_NAMES.objcpp_compile,
304        ],
305        flag_groups = [
306            flag_group(
307                flags = [
308                    "-std=c++17",
309                ],
310            ),
311        ],
312    )
313
314    # copts and defines appear to not automatically be set
315    # https://bazel.build/docs/cc-toolchain-config-reference#cctoolchainconfiginfo-build-variables
316    # https://github.com/bazelbuild/bazel/blob/5ad4a6126be2bdc53ee7e2457e076c90efe86d56/tools/cpp/cc_toolchain_config_lib.bzl#L200-L209
317    objc_compile_flags = flag_set(
318        actions = [
319            ACTION_NAMES.objc_compile,
320            ACTION_NAMES.objcpp_compile,
321        ],
322        flag_groups = [
323            flag_group(
324                iterate_over = "user_compile_flags",
325                flags = ["%{user_compile_flags}"],
326            ),
327            flag_group(
328                iterate_over = "preprocessor_defines",
329                flags = ["-D%{preprocessor_defines}"],
330            ),
331        ],
332    )
333
334    link_exe_flags = flag_set(
335        actions = [
336            ACTION_NAMES.cpp_link_executable,
337            ACTION_NAMES.cpp_link_dynamic_library,
338            ACTION_NAMES.cpp_link_nodeps_dynamic_library,
339        ],
340        flag_groups = [
341            flag_group(
342                flags = [
343                    # lld goes through dynamic library dependencies for dylib and tbh files through
344                    # absolute paths (/System/Library/Frameworks). However, the dependencies live in
345                    # [Xcode dir]/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks
346                    # -Wl tells clang to forward the next flag to the linker.
347                    # -syslibroot appends to the beginning of the dylib dependency path.
348                    # https://github.com/llvm/llvm-project/blob/d61341768cf0cff7ceeaddecc2f769b5c1b901c4/lld/MachO/InputFiles.cpp#L1418-L1420
349                    "-Wl,-syslibroot",
350                    XCODE_MACSDK_SYMLINK,
351                    # This path is relative to the syslibroot above, and we want lld to look in the
352                    # Frameworks symlink that was created in download_mac_toolchain.bzl.
353                    "-F/System/Library/Frameworks",
354                    "-fuse-ld=lld",
355                    "-std=c++17",
356                    "-stdlib=libc++",
357                    EXTERNAL_TOOLCHAIN + "/lib/libc++.a",
358                    EXTERNAL_TOOLCHAIN + "/lib/libc++abi.a",
359                    EXTERNAL_TOOLCHAIN + "/lib/libunwind.a",
360                ],
361            ),
362        ],
363    )
364
365    return [feature(
366        "default_flags",
367        enabled = True,
368        flag_sets = [
369            cpp_compile_flags,
370            cxx_compile_includes,
371            link_exe_flags,
372            objc_compile_flags,
373        ],
374    )]
375
376def _make_diagnostic_flags():
377    """Here we define the flags that can be turned on via features to yield debug info."""
378    cxx_diagnostic = flag_set(
379        actions = [
380            ACTION_NAMES.c_compile,
381            ACTION_NAMES.cpp_compile,
382        ],
383        flag_groups = [
384            flag_group(
385                flags = [
386                    "--trace-includes",
387                    "-v",
388                ],
389            ),
390        ],
391    )
392
393    link_diagnostic = flag_set(
394        actions = [ACTION_NAMES.cpp_link_executable],
395        flag_groups = [
396            flag_group(
397                flags = [
398                    "-Wl,--verbose",
399                    "-v",
400                ],
401            ),
402        ],
403    )
404
405    link_search_dirs = flag_set(
406        actions = [ACTION_NAMES.cpp_link_executable],
407        flag_groups = [
408            flag_group(
409                flags = [
410                    "--print-search-dirs",
411                ],
412            ),
413        ],
414    )
415    return [
416        # Running a Bazel command with --features diagnostic will cause the compilation and
417        # link steps to be more verbose.
418        feature(
419            "diagnostic",
420            enabled = False,
421            flag_sets = [
422                cxx_diagnostic,
423                link_diagnostic,
424            ],
425        ),
426        feature(
427            "link_diagnostic",
428            enabled = False,
429            flag_sets = [
430                link_diagnostic,
431            ],
432        ),
433        # Running a Bazel command with --features print_search_dirs will cause the link to fail
434        # but directories searched for libraries, etc will be displayed.
435        feature(
436            "print_search_dirs",
437            enabled = False,
438            flag_sets = [
439                link_search_dirs,
440            ],
441        ),
442    ]
443
444# The parameter is of type https://bazel.build/rules/lib/ctx
445def _make_target_specific_flags(ctx):
446    m1_mac_target = flag_set(
447        actions = [
448            ACTION_NAMES.assemble,
449            ACTION_NAMES.preprocess_assemble,
450            ACTION_NAMES.c_compile,
451            ACTION_NAMES.cpp_compile,
452            ACTION_NAMES.objc_compile,
453            ACTION_NAMES.objcpp_compile,
454            ACTION_NAMES.cpp_link_executable,
455            ACTION_NAMES.cpp_link_dynamic_library,
456        ],
457        flag_groups = [
458            flag_group(
459                flags = [
460                    "--target=arm64-apple-macos12",
461                ],
462            ),
463        ],
464    )
465    intel_mac_target = flag_set(
466        actions = [
467            ACTION_NAMES.assemble,
468            ACTION_NAMES.preprocess_assemble,
469            ACTION_NAMES.c_compile,
470            ACTION_NAMES.cpp_compile,
471            ACTION_NAMES.objc_compile,
472            ACTION_NAMES.objcpp_compile,
473            ACTION_NAMES.cpp_link_executable,
474            ACTION_NAMES.cpp_link_dynamic_library,
475        ],
476        flag_groups = [
477            flag_group(
478                flags = [
479                    "--target=x86_64-apple-macos12",
480                ],
481            ),
482        ],
483    )
484
485    target_specific_features = []
486    if _has_platform_constraint(ctx, "@platforms//cpu:arm64"):
487        target_specific_features.append(
488            feature(
489                name = "_m1_mac_target",
490                enabled = True,
491                flag_sets = [m1_mac_target],
492            ),
493        )
494    elif _has_platform_constraint(ctx, "@platforms//cpu:x86_64"):
495        target_specific_features.append(
496            feature(
497                name = "_intel_mac_target",
498                enabled = True,
499                flag_sets = [intel_mac_target],
500            ),
501        )
502
503    return target_specific_features
504