From cc396794ccc67622d336f897ec826927f5502db7 Mon Sep 17 00:00:00 2001 From: Chirag Ramani Date: Wed, 13 Mar 2024 12:44:51 -0700 Subject: [PATCH] Bundle libMainThreadChecker.dylib to detect main thread violations (#2277) This PR introduces the integration of libMainThreadChecker.dylib to enhance quality checks by detecting main thread violations. By passing the feature=include_main_thread_checker flag, the library `libMainThreadChecker.dylib` will be embedded. Additionally, modifications to the test runner implementation ensure the DYLD_INSERT_LIBRARIES is updated to recognize this dylib. This aims to bolster both unit and UI test reliability. https://developer.apple.com/documentation/xcode/diagnosing-memory-thread-and-crash-issues-early --- apple/internal/BUILD | 7 + apple/internal/apple_toolchains.bzl | 13 ++ apple/internal/ios_rules.bzl | 58 ++++++++ apple/internal/macos_rules.bzl | 85 +++++++++++ apple/internal/partials.bzl | 5 + apple/internal/partials/BUILD | 15 ++ .../partials/main_thread_checker_dylibs.bzl | 137 ++++++++++++++++++ .../testing/apple_test_bundle_support.bzl | 13 ++ .../testing/apple_test_rule_support.bzl | 26 ++++ apple/internal/tvos_rules.bzl | 40 +++++ apple/internal/utils/BUILD | 6 + .../utils/main_thread_checker_dylibs.bzl | 40 +++++ apple/internal/visionos_rules.bzl | 40 +++++ apple/internal/watchos_rules.bzl | 40 +++++ .../ios_test_runner.template.sh | 22 ++- .../ios_xctestrun_runner.template.sh | 10 ++ test/ios_application_swift_test.sh | 19 +++ test/ios_test_runner_unit_test.sh | 76 ++++++++++ test/macos_application_test.sh | 13 ++ tools/BUILD | 1 + tools/main_thread_checker_tool/BUILD | 21 +++ tools/main_thread_checker_tool/README | 1 + .../main_thread_checker_tool.py | 78 ++++++++++ 23 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 apple/internal/partials/main_thread_checker_dylibs.bzl create mode 100644 apple/internal/utils/main_thread_checker_dylibs.bzl create mode 100644 tools/main_thread_checker_tool/BUILD create mode 100644 tools/main_thread_checker_tool/README create mode 100644 tools/main_thread_checker_tool/main_thread_checker_tool.py diff --git a/apple/internal/BUILD b/apple/internal/BUILD index 47243558b8..5ef390f8fc 100644 --- a/apple/internal/BUILD +++ b/apple/internal/BUILD @@ -27,6 +27,7 @@ apple_mac_tools_toolchain( dsym_info_plist_template = "//apple/internal/templates:dsym_info_plist_template", environment_plist_tool = "//tools/environment_plist", imported_dynamic_framework_processor = "//tools/imported_dynamic_framework_processor", + main_thread_checker_tool = "//tools/main_thread_checker_tool", plisttool = "//tools/plisttool", process_and_sign_template = "//tools/bundletool:process_and_sign_template", provisioning_profile_tool = "//tools/provisioning_profile_tool", @@ -319,6 +320,7 @@ bzl_library( "//apple/internal/aspects:framework_provider_aspect", "//apple/internal/aspects:resource_aspect", "//apple/internal/utils:clang_rt_dylibs", + "//apple/internal/utils:main_thread_checker_dylibs", "@bazel_skylib//lib:collections", "@bazel_tools//tools/cpp:toolchain_utils.bzl", "@build_bazel_rules_swift//swift", @@ -393,6 +395,7 @@ bzl_library( "//apple/internal/aspects:framework_provider_aspect", "//apple/internal/aspects:resource_aspect", "//apple/internal/utils:clang_rt_dylibs", + "//apple/internal/utils:main_thread_checker_dylibs", ], ) @@ -431,6 +434,7 @@ bzl_library( "//apple/internal/partials:framework_import", "//apple/internal/partials:framework_provider", "//apple/internal/partials:macos_additional_contents", + "//apple/internal/partials:main_thread_checker_dylibs", "//apple/internal/partials:messages_stub", "//apple/internal/partials:provisioning_profile", "//apple/internal/partials:resources", @@ -663,6 +667,7 @@ bzl_library( "//apple/internal/aspects:framework_provider_aspect", "//apple/internal/aspects:resource_aspect", "//apple/internal/utils:clang_rt_dylibs", + "//apple/internal/utils:main_thread_checker_dylibs", "@bazel_tools//tools/cpp:toolchain_utils.bzl", "@build_bazel_rules_swift//swift", ], @@ -698,6 +703,7 @@ bzl_library( "//apple/internal/aspects:framework_provider_aspect", "//apple/internal/aspects:resource_aspect", "//apple/internal/utils:clang_rt_dylibs", + "//apple/internal/utils:main_thread_checker_dylibs", "@bazel_skylib//lib:sets", "@bazel_tools//tools/cpp:toolchain_utils.bzl", ], @@ -732,6 +738,7 @@ bzl_library( "//apple/internal/aspects:framework_provider_aspect", "//apple/internal/aspects:resource_aspect", "//apple/internal/utils:clang_rt_dylibs", + "//apple/internal/utils:main_thread_checker_dylibs", "@bazel_skylib//lib:sets", "@bazel_tools//tools/cpp:toolchain_utils.bzl", ], diff --git a/apple/internal/apple_toolchains.bzl b/apple/internal/apple_toolchains.bzl index 548e749f24..dfc6aa44e1 100644 --- a/apple/internal/apple_toolchains.bzl +++ b/apple/internal/apple_toolchains.bzl @@ -61,6 +61,10 @@ A `struct` from `ctx.resolve_tools` referencing a tool to process an imported dy such that the given framework only contains the same slices as the app binary, every file belonging to the dynamic framework is copied to a temporary location, and the dynamic framework is codesigned and zipped as a cacheable artifact. +""", + "resolved_main_thread_checker_tool": """\ +A `struct` from `ctx.resolve_tools` referencing a tool to find libMainThreadChecker.dylib linked to a +binary. """, "resolved_plisttool": """\ A `struct` from `ctx.resolve_tools` referencing a tool to perform plist operations such as variable @@ -168,6 +172,10 @@ def _apple_mac_tools_toolchain_impl(ctx): attr_name = "clangrttool", rule_ctx = ctx, ), + resolved_main_thread_checker_tool = _resolve_tools_for_executable( + attr_name = "main_thread_checker_tool", + rule_ctx = ctx, + ), resolved_environment_plist_tool = _resolve_tools_for_executable( attr_name = "environment_plist_tool", rule_ctx = ctx, @@ -255,6 +263,11 @@ copied to a temporary location, and the dynamic framework is codesigned and zipp artifact. """, ), + "main_thread_checker_tool": attr.label( + cfg = "target", + executable = True, + doc = "A `File` referencing a tool to find libMainThreadChecker.dylib linked to a binary.", + ), "plisttool": attr.label( cfg = "target", executable = True, diff --git a/apple/internal/ios_rules.bzl b/apple/internal/ios_rules.bzl index 64e7b4f73b..fa2261145a 100644 --- a/apple/internal/ios_rules.bzl +++ b/apple/internal/ios_rules.bzl @@ -26,6 +26,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", "apple_product_type", @@ -319,6 +323,15 @@ def _ios_application_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -651,6 +664,15 @@ def _ios_app_clip_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -946,6 +968,15 @@ def _ios_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -1234,6 +1265,15 @@ def _ios_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -1505,6 +1545,15 @@ def _ios_dynamic_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -2138,6 +2187,15 @@ def _ios_imessage_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, diff --git a/apple/internal/macos_rules.bzl b/apple/internal/macos_rules.bzl index 42f7590831..90560fb823 100644 --- a/apple/internal/macos_rules.bzl +++ b/apple/internal/macos_rules.bzl @@ -112,6 +112,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_apple//apple/internal:cc_info_support.bzl", "cc_info_support", @@ -280,6 +284,15 @@ def _macos_application_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -571,6 +584,15 @@ def _macos_bundle_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -823,6 +845,15 @@ def _macos_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1072,6 +1103,15 @@ def _macos_quick_look_plugin_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1313,6 +1353,15 @@ def _macos_kernel_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1544,6 +1593,15 @@ def _macos_spotlight_importer_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1774,6 +1832,15 @@ def _macos_xpc_service_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -2818,6 +2885,15 @@ def _macos_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -3087,6 +3163,15 @@ def _macos_dynamic_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, diff --git a/apple/internal/partials.bzl b/apple/internal/partials.bzl index 7ebe2911ff..b7717a4080 100644 --- a/apple/internal/partials.bzl +++ b/apple/internal/partials.bzl @@ -38,6 +38,10 @@ load( "@build_bazel_rules_apple//apple/internal/partials:clang_rt_dylibs.bzl", _clang_rt_dylibs_partial = "clang_rt_dylibs_partial", ) +load( + "@build_bazel_rules_apple//apple/internal/partials:main_thread_checker_dylibs.bzl", + _main_thread_checker_dylibs_partial = "main_thread_checker_dylibs_partial", +) load( "@build_bazel_rules_apple//apple/internal/partials:codesigning_dossier.bzl", _codesigning_dossier_partial = "codesigning_dossier_partial", @@ -113,6 +117,7 @@ partials = struct( apple_bundle_info_partial = _apple_bundle_info_partial, binary_partial = _binary_partial, clang_rt_dylibs_partial = _clang_rt_dylibs_partial, + main_thread_checker_dylibs_partial = _main_thread_checker_dylibs_partial, codesigning_dossier_partial = _codesigning_dossier_partial, debug_symbols_partial = _debug_symbols_partial, embedded_bundles_partial = _embedded_bundles_partial, diff --git a/apple/internal/partials/BUILD b/apple/internal/partials/BUILD index 7d766731e7..cd4060c853 100644 --- a/apple/internal/partials/BUILD +++ b/apple/internal/partials/BUILD @@ -91,6 +91,21 @@ bzl_library( ], ) +bzl_library( + name = "main_thread_checker_dylibs", + srcs = ["main_thread_checker_dylibs.bzl"], + visibility = [ + "//apple/internal:__pkg__", + ], + deps = [ + "//apple/internal:intermediates", + "//apple/internal:processor", + "//apple/internal/utils:main_thread_checker_dylibs", + "@bazel_skylib//lib:partial", + "@build_bazel_apple_support//lib:apple_support", + ], +) + bzl_library( name = "codesigning_dossier", srcs = ["codesigning_dossier.bzl"], diff --git a/apple/internal/partials/main_thread_checker_dylibs.bzl b/apple/internal/partials/main_thread_checker_dylibs.bzl new file mode 100644 index 0000000000..b627cc3aaf --- /dev/null +++ b/apple/internal/partials/main_thread_checker_dylibs.bzl @@ -0,0 +1,137 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Partial implementation for Main Thread Checker libraries processing.""" + +load( + "@build_bazel_rules_apple//apple/internal:intermediates.bzl", + "intermediates", +) +load( + "@build_bazel_rules_apple//apple/internal:processor.bzl", + "processor", +) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) +load( + "@build_bazel_apple_support//lib:apple_support.bzl", + "apple_support", +) +load( + "@bazel_skylib//lib:partial.bzl", + "partial", +) + +def _should_include_main_thread_checker(features): + return main_thread_checker_dylibs.should_package_main_thread_checker_dylib(features = features) + +def _create_main_thread_checker_dylib(actions, label_name, output_discriminator): + return intermediates.file( + actions = actions, + target_name = label_name, + output_discriminator = output_discriminator, + file_name = "libMainThreadChecker.dylib", + ) + +def _run_main_thread_checker( + actions, + binary_artifact, + dylibs, + main_thread_checker_dylib, + platform_prerequisites, + resolved_main_thread_checker_tool): + apple_support.run( + actions = actions, + apple_fragment = platform_prerequisites.apple_fragment, + arguments = [ + binary_artifact.path, + main_thread_checker_dylib.path, + ], + executable = resolved_main_thread_checker_tool.files_to_run, + execution_requirements = {"no-sandbox": "1"}, + inputs = depset([binary_artifact] + dylibs, transitive = [resolved_main_thread_checker_tool.inputs]), + input_manifests = resolved_main_thread_checker_tool.input_manifests, + outputs = [main_thread_checker_dylib], + mnemonic = "MainThreadCheckerLibsCopy", + xcode_config = platform_prerequisites.xcode_version_config, + ) + +def _main_thread_checker_dylibs_partial_impl( + *, + actions, + apple_mac_toolchain_info, + binary_artifact, + features, + label_name, + output_discriminator, + platform_prerequisites, + dylibs): + """Implementation for the Main Thread Checker dylibs processing partial.""" + bundle_files = [] + + if not _should_include_main_thread_checker(features = features): + return struct(bundle_files = bundle_files) + + main_thread_checker_dylib = _create_main_thread_checker_dylib(actions, label_name, output_discriminator) + resolved_main_thread_checker_tool = apple_mac_toolchain_info.resolved_main_thread_checker_tool + + _run_main_thread_checker(actions, binary_artifact, dylibs, main_thread_checker_dylib, platform_prerequisites, resolved_main_thread_checker_tool) + + bundle_files.append( + (processor.location.framework, None, depset([main_thread_checker_dylib])), + ) + + return struct(bundle_files = bundle_files) + +def main_thread_checker_dylibs_partial( + *, + actions, + apple_mac_toolchain_info, + binary_artifact, + dylibs, + features, + label_name, + output_discriminator = None, + platform_prerequisites): + """Constructor for the Main Thread Checker dylibs processing partial. + + Args: + actions: The actions provider from `ctx.actions`. + apple_mac_toolchain_info: `struct` of tools from the shared Apple toolchain. + binary_artifact: The main binary artifact for this target. + dylibs: List of dylibs (usually from a toolchain). + features: List of features enabled by the user. Typically from `ctx.features`. + label_name: Name of the target being built. + output_discriminator: A string to differentiate between different target intermediate files + or `None`. + platform_prerequisites: Struct containing information on the platform being targeted. + dylibs: The Main Thread Checker dylibs to bundle with the target. + + Returns: + A partial that returns the bundle location of the Main Thread Checker dylib, if there were any to + bundle. + """ + return partial.make( + _main_thread_checker_dylibs_partial_impl, + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label_name, + output_discriminator = output_discriminator, + platform_prerequisites = platform_prerequisites, + dylibs = dylibs, + ) diff --git a/apple/internal/testing/apple_test_bundle_support.bzl b/apple/internal/testing/apple_test_bundle_support.bzl index c3070f201f..411372a0ba 100644 --- a/apple/internal/testing/apple_test_bundle_support.bzl +++ b/apple/internal/testing/apple_test_bundle_support.bzl @@ -80,6 +80,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_apple//apple:providers.bzl", "AppleBundleInfo", @@ -443,6 +447,15 @@ def _apple_test_bundle_impl(*, ctx, product_type): provisioning_profile = provisioning_profile, rule_descriptor = rule_descriptor, ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, diff --git a/apple/internal/testing/apple_test_rule_support.bzl b/apple/internal/testing/apple_test_rule_support.bzl index b1aaef3072..b27f934988 100644 --- a/apple/internal/testing/apple_test_rule_support.bzl +++ b/apple/internal/testing/apple_test_rule_support.bzl @@ -170,9 +170,32 @@ def _get_coverage_execution_environment(*, covered_binaries): "TEST_BINARIES_FOR_LLVM_COV": ";".join(covered_binary_paths), } +def _get_main_thread_checker_test_environment(*, features): + """Returns environment variables required for supporting Main Thread Checker during testing. + + Args: + features: List of features enabled by the user. Typically from `ctx.features`. + Returns: + dict: A dictionary containing the required environment variables for enabling Main Thread Checker + during testing. + + This function checks for the presence of the "apple.fail_on_main_thread_checker" feature in the list of features. + If the feature is present, it returns a dictionary with the "MTC_CRASH_ON_REPORT" variable set to "1", enabling + Main Thread Checker crash reporting, which may cause test execution to halt upon violations. If the feature is + not present, an empty dictionary is returned, meaning that Main Thread Checker violations will still be reported, + but they won't cause the test execution to crash. + """ + + if "apple.fail_on_main_thread_checker" in features: + return { + "MTC_CRASH_ON_REPORT": "1", + } + return {} + def _get_simulator_test_environment( *, command_line_test_env, + features, rule_test_env, runner_test_env): """Returns the test environment for the current process running in the simulator @@ -183,6 +206,7 @@ def _get_simulator_test_environment( Args: command_line_test_env: Dictionary of fhe environment variables retrieved from the test invocation's command line arguments. + features: List of features enabled by the user. Typically from `ctx.features`. rule_test_env: Dictionary of the environment variables retrieved from the test rule's attributes. runner_test_env: Dictionary of the environment variables retrieved from the assigned test @@ -226,6 +250,7 @@ def _get_simulator_test_environment( rule_test_env_copy, runner_test_env_copy, test_env_dyld_insert_pairs, + _get_main_thread_checker_test_environment(features = features), ) def _apple_test_rule_impl(*, ctx, requires_dossiers, test_type): @@ -260,6 +285,7 @@ def _apple_test_rule_impl(*, ctx, requires_dossiers, test_type): # --test_env and env attribute values, but not the execution environment variables. test_environment = _get_simulator_test_environment( command_line_test_env = ctx.configuration.test_env, + features = ctx.features, rule_test_env = ctx.attr.env, runner_test_env = getattr(runner_info, "test_environment", {}), ) diff --git a/apple/internal/tvos_rules.bzl b/apple/internal/tvos_rules.bzl index 59d32cce9d..7867434a55 100644 --- a/apple/internal/tvos_rules.bzl +++ b/apple/internal/tvos_rules.bzl @@ -23,6 +23,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", "apple_product_type", @@ -271,6 +275,15 @@ def _tvos_application_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -596,6 +609,15 @@ def _tvos_dynamic_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -862,6 +884,15 @@ def _tvos_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1132,6 +1163,15 @@ def _tvos_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, diff --git a/apple/internal/utils/BUILD b/apple/internal/utils/BUILD index 40a0e3adcb..e39b94d8fd 100644 --- a/apple/internal/utils/BUILD +++ b/apple/internal/utils/BUILD @@ -23,6 +23,12 @@ bzl_library( deps = ["@bazel_tools//tools/cpp:toolchain_utils.bzl"], ) +bzl_library( + name = "main_thread_checker_dylibs", + srcs = ["main_thread_checker_dylibs.bzl"], + deps = ["@bazel_tools//tools/cpp:toolchain_utils.bzl"], +) + bzl_library( name = "defines", srcs = ["defines.bzl"], diff --git a/apple/internal/utils/main_thread_checker_dylibs.bzl b/apple/internal/utils/main_thread_checker_dylibs.bzl new file mode 100644 index 0000000000..066393a75d --- /dev/null +++ b/apple/internal/utils/main_thread_checker_dylibs.bzl @@ -0,0 +1,40 @@ +# Copyright 2023 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Support functions related to getting main thread checker libraries.""" + +load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") + +def _should_package_main_thread_checker_dylib(*, features): + """Returns whether the libMainThreadChecker.dylib should be bundled.""" + + return "apple.include_main_thread_checker" in features + +def _get_from_toolchain(ctx): + if hasattr(ctx.attr, "_cc_toolchain"): + cc_toolchain = find_cpp_toolchain(ctx) + dylibs = [ + x + for x in cc_toolchain.all_files.to_list() + if x.basename.startswith("libMainThreadChecker") and x.basename.endswith(".dylib") + ] + else: + dylibs = [] + + return dylibs + +main_thread_checker_dylibs = struct( + should_package_main_thread_checker_dylib = _should_package_main_thread_checker_dylib, + get_from_toolchain = _get_from_toolchain, +) diff --git a/apple/internal/visionos_rules.bzl b/apple/internal/visionos_rules.bzl index 28ca41e909..99364076ad 100644 --- a/apple/internal/visionos_rules.bzl +++ b/apple/internal/visionos_rules.bzl @@ -125,6 +125,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_swift//swift:swift.bzl", "SwiftInfo", @@ -275,6 +279,15 @@ Resolved Xcode is version {xcode_version}. platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -593,6 +606,15 @@ def _visionos_dynamic_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -856,6 +878,15 @@ def _visionos_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1122,6 +1153,15 @@ def _visionos_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, diff --git a/apple/internal/watchos_rules.bzl b/apple/internal/watchos_rules.bzl index f0553efbc5..4f44e62dd2 100644 --- a/apple/internal/watchos_rules.bzl +++ b/apple/internal/watchos_rules.bzl @@ -110,6 +110,10 @@ load( "@build_bazel_rules_apple//apple/internal/utils:clang_rt_dylibs.bzl", "clang_rt_dylibs", ) +load( + "@build_bazel_rules_apple//apple/internal/utils:main_thread_checker_dylibs.bzl", + "main_thread_checker_dylibs", +) load( "@build_bazel_rules_apple//apple/internal:framework_import_support.bzl", "libraries_to_link_for_dynamic_framework", @@ -281,6 +285,15 @@ def _watchos_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -549,6 +562,15 @@ def _watchos_dynamic_framework_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.debug_symbols_partial( actions = actions, bundle_extension = bundle_extension, @@ -1159,6 +1181,15 @@ def _watchos_extension_impl(ctx): platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, @@ -1613,6 +1644,15 @@ delegate is referenced in the single-target `watchos_application`'s `deps`. platform_prerequisites = platform_prerequisites, dylibs = clang_rt_dylibs.get_from_toolchain(ctx), ), + partials.main_thread_checker_dylibs_partial( + actions = actions, + apple_mac_toolchain_info = apple_mac_toolchain_info, + binary_artifact = binary_artifact, + features = features, + label_name = label.name, + platform_prerequisites = platform_prerequisites, + dylibs = main_thread_checker_dylibs.get_from_toolchain(ctx), + ), partials.codesigning_dossier_partial( actions = actions, apple_mac_toolchain_info = apple_mac_toolchain_info, diff --git a/apple/testing/default_runner/ios_test_runner.template.sh b/apple/testing/default_runner/ios_test_runner.template.sh index ae52a8012f..b4e1b27b93 100644 --- a/apple/testing/default_runner/ios_test_runner.template.sh +++ b/apple/testing/default_runner/ios_test_runner.template.sh @@ -116,10 +116,30 @@ for sanitizer in "$sanitizer_root"/libclang_rt.*.dylib; do sanitizer_dyld_env="${sanitizer_dyld_env}${sanitizer}" done +main_thread_checker_dyld_env="" +readonly main_thread_checker_root="$TEST_BUNDLE_PATH/Frameworks" +main_thread_checker="$main_thread_checker_root/libMainThreadChecker.dylib" +if [[ -e "$main_thread_checker" ]]; then + main_thread_checker_dyld_env="$main_thread_checker" +fi + +DYLD_INSERT_LIBRARIES_VALUE="" + +if [[ -n "$main_thread_checker_dyld_env" ]]; then + if [[ -n "$DYLD_INSERT_LIBRARIES_VALUE" ]]; then + DYLD_INSERT_LIBRARIES_VALUE="$DYLD_INSERT_LIBRARIES_VALUE:" + fi + DYLD_INSERT_LIBRARIES_VALUE="$DYLD_INSERT_LIBRARIES_VALUE$main_thread_checker_dyld_env" +fi + if [[ -n "$sanitizer_dyld_env" ]]; then - TEST_ENV="$TEST_ENV,DYLD_INSERT_LIBRARIES=$sanitizer_dyld_env" + if [[ -n "$DYLD_INSERT_LIBRARIES_VALUE" ]]; then + DYLD_INSERT_LIBRARIES_VALUE="$DYLD_INSERT_LIBRARIES_VALUE:" + fi + DYLD_INSERT_LIBRARIES_VALUE="$DYLD_INSERT_LIBRARIES_VALUE$sanitizer_dyld_env" fi +TEST_ENV="$TEST_ENV,DYLD_INSERT_LIBRARIES=$DYLD_INSERT_LIBRARIES_VALUE" readonly profraw="$TMP_DIR/coverage.profraw" if [[ "${COVERAGE:-}" -eq 1 ]]; then readonly profile_env="LLVM_PROFILE_FILE=$profraw" diff --git a/apple/testing/default_runner/ios_xctestrun_runner.template.sh b/apple/testing/default_runner/ios_xctestrun_runner.template.sh index abbb1bee4d..6a61e878c4 100755 --- a/apple/testing/default_runner/ios_xctestrun_runner.template.sh +++ b/apple/testing/default_runner/ios_xctestrun_runner.template.sh @@ -264,10 +264,20 @@ for sanitizer in "$sanitizer_root"/libclang_rt.*.dylib; do sanitizer_dyld_env="${sanitizer_dyld_env}${sanitizer}" done +main_thread_checker_dyld_env="" +readonly main_thread_checker_root="$test_tmp_dir/$test_bundle_name.xctest/Frameworks" +main_thread_checker="$main_thread_checker_root/libMainThreadChecker.dylib" +if [[ -e "$main_thread_checker" ]]; then + main_thread_checker_dyld_env="$main_thread_checker" +fi + xctestrun_libraries="__PLATFORMS__/$test_execution_platform/Developer/usr/lib/libXCTestBundleInject.dylib" if [[ -n "$sanitizer_dyld_env" ]]; then xctestrun_libraries="${xctestrun_libraries}:${sanitizer_dyld_env}" fi +if [[ -n "$main_thread_checker_dyld_env" ]]; then + xctestrun_libraries="${xctestrun_libraries}:${main_thread_checker_dyld_env}" +fi TEST_FILTER="%(test_filter)s" xctestrun_skip_test_section="" diff --git a/test/ios_application_swift_test.sh b/test/ios_application_swift_test.sh index 9f0aa24900..cdaeb32823 100755 --- a/test/ios_application_swift_test.sh +++ b/test/ios_application_swift_test.sh @@ -295,4 +295,23 @@ EOF fi } +# Tests that app builds with include_main_thread_checker +# and that the libMainThreadChecker.dylib is packaged into the IPA when enabled. +function test_swift_builds_with_include_main_thread_checker() { + create_minimal_ios_application + + cat >> app/BUILD < ios/main_thread_checker_violation.swift < ios/MainThreadCheckerViolationTest-Info.plist < + + CFBundleExecutable + MainThreadCheckerViolationTest + + +EOF + + cat >> ios/BUILD <