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