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