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