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