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