diff --git a/apple/BUILD b/apple/BUILD index f3b15ad04b..3d7cf878d5 100644 --- a/apple/BUILD +++ b/apple/BUILD @@ -50,6 +50,18 @@ bzl_library( ], ) +bzl_library( + name = "apple_static_library", + srcs = ["apple_static_library.bzl"], + deps = [ + ":providers", + "//apple/internal:linking_support", + "//apple/internal:rule_factory", + "//apple/internal:transition_support", + "@bazel_skylib//lib:dicts", + ], +) + bzl_library( name = "aspects", srcs = ["aspects.bzl"], diff --git a/apple/apple_static_library.bzl b/apple/apple_static_library.bzl new file mode 100644 index 0000000000..ba5d4aeafd --- /dev/null +++ b/apple/apple_static_library.bzl @@ -0,0 +1,174 @@ +# Copyright 2020 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. + +"""apple_static_library Starlark implementation""" + +load( + "@build_bazel_rules_apple//apple/internal:transition_support.bzl", + "transition_support", +) +load( + "@build_bazel_rules_apple//apple/internal:linking_support.bzl", + "linking_support", +) +load( + "@build_bazel_rules_apple//apple/internal:rule_factory.bzl", + "rule_factory", +) +load( + "@build_bazel_rules_apple//apple:providers.bzl", + "AppleBinaryInfo", +) +load( + "@bazel_skylib//lib:dicts.bzl", + "dicts", +) + +def _apple_static_library_impl(ctx): + # Validation of the platform type and minimum version OS currently happen in + # `transition_support.apple_platform_transition`, either implicitly through native + # `dotted_version` or explicitly through `fail` on an unrecognized platform type value. + + link_result = linking_support.register_static_library_linking_action(ctx = ctx) + + files_to_build = [link_result.library] + runfiles = ctx.runfiles( + files = files_to_build, + collect_default = True, + collect_data = True, + ) + + return [ + DefaultInfo(files = depset(files_to_build), runfiles = runfiles), + AppleBinaryInfo( + binary = link_result.library, + ), + link_result.objc, + link_result.output_groups, + ] + +apple_static_library = rule( + implementation = _apple_static_library_impl, + attrs = dicts.add( + rule_factory.common_tool_attributes, + rule_factory.common_bazel_attributes.link_multi_arch_static_library_attrs( + cfg = apple_common.multi_arch_split, + ), + { + "additional_linker_inputs": attr.label_list( + # Flag required for compile_one_dependency + flags = ["DIRECT_COMPILE_TIME_INPUT"], + allow_files = True, + doc = """ +A list of input files to be passed to the linker. +""", + ), + "avoid_deps": attr.label_list( + cfg = apple_common.multi_arch_split, + providers = [CcInfo], + # Flag required for compile_one_dependency + flags = ["DIRECT_COMPILE_TIME_INPUT"], + doc = """ +A list of library targets on which this framework depends in order to compile, but the transitive +closure of which will not be linked into the framework's binary. +""", + ), + "data": attr.label_list( + allow_files = True, + doc = """ +Files to be made available to the library archive upon execution. +""", + ), + "deps": attr.label_list( + cfg = apple_common.multi_arch_split, + providers = [CcInfo], + # Flag required for compile_one_dependency + flags = ["DIRECT_COMPILE_TIME_INPUT"], + doc = """ +A list of dependencies targets that will be linked into this target's binary. Any resources, such as +asset catalogs, that are referenced by those targets will also be transitively included in the final +bundle. +""", + ), + "linkopts": attr.string_list( + doc = """ +A list of strings representing extra flags that should be passed to the linker. +""", + ), + "minimum_os_version": attr.string( + mandatory = True, + doc = """ +A required string indicating the minimum OS version supported by the target, represented as a +dotted version number (for example, "9.0"). +""", + ), + "platform_type": attr.string( + mandatory = True, + doc = """ +The target Apple platform for which to create a binary. This dictates which SDK +is used for compilation/linking and which flag is used to determine the +architectures to target. For example, if `ios` is specified, then the output +binaries/libraries will be created combining all architectures specified by +`--ios_multi_cpus`. Options are: + +* `ios`: architectures gathered from `--ios_multi_cpus`. +* `macos`: architectures gathered from `--macos_cpus`. +* `tvos`: architectures gathered from `--tvos_cpus`. +* `watchos`: architectures gathered from `--watchos_cpus`. +""", + ), + "sdk_frameworks": attr.string_list( + doc = """ +Names of SDK frameworks to link with (e.g., `AddressBook`, `QuartzCore`). +`UIKit` and `Foundation` are always included, even if this attribute is +provided and does not list them. + +This attribute is discouraged; in general, targets should list system +framework dependencies in the library targets where that framework is used, +not in the top-level bundle. +""", + ), + "sdk_dylibs": attr.string_list( + doc = """ +Names of SDK `.dylib` libraries to link with (e.g., `libz` or `libarchive`). +`libc++` is included automatically if the binary has any C++ or Objective-C++ +sources in its dependency tree. When linking a binary, all libraries named in +that binary's transitive dependency graph are used. +""", + ), + "weak_sdk_frameworks": attr.string_list( + doc = """ +Names of SDK frameworks to weakly link with (e.g., `MediaAccessibility`). +Unlike regularly linked SDK frameworks, symbols from weakly linked +frameworks do not cause the binary to fail to load if they are not present in +the version of the framework available at runtime. + +This attribute is discouraged; in general, targets should list system +framework dependencies in the library targets where that framework is used, +not in the top-level bundle. +""", + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, + ), + outputs = { + "lipo_archive": "%{name}_lipo.a", + }, + cfg = transition_support.apple_platform_transition, + fragments = ["objc", "apple", "cpp"], + toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], + incompatible_use_toolchain_transition = True, +) diff --git a/apple/internal/linking_support.bzl b/apple/internal/linking_support.bzl index 14d65c027b..619b5bb684 100644 --- a/apple/internal/linking_support.bzl +++ b/apple/internal/linking_support.bzl @@ -261,6 +261,7 @@ def _register_static_library_linking_action(ctx): return struct( library = fat_library, + objc = linking_outputs.objc, outputs = linking_outputs.outputs, output_groups = linking_outputs.output_groups, ) diff --git a/apple/internal/transition_support.bzl b/apple/internal/transition_support.bzl index 55c41a6ac5..e435740918 100644 --- a/apple/internal/transition_support.bzl +++ b/apple/internal/transition_support.bzl @@ -106,6 +106,7 @@ def _command_line_options( emit_swiftinterface = False, minimum_os_version, platform_type, + platforms = [], settings): """Generates a dictionary of command line options suitable for the current target. @@ -118,6 +119,8 @@ def _command_line_options( platform, represented as a dotted version number (for example, `"9.0"`). platform_type: The Apple platform for which the rule should build its targets (`"ios"`, `"macos"`, `"tvos"`, or `"watchos"`). + platforms: A list of labels referencing platforms if any should be set by the current rule. + Defaults to an empty list so that platform mapping can take place. settings: A dictionary whose set of keys is defined by the inputs parameter, typically from the settings argument found on the implementation function of the current Starlark transition. @@ -141,6 +144,7 @@ def _command_line_options( ), "//command_line_option:fission": [], "//command_line_option:grte_top": settings["//command_line_option:apple_grte_top"], + "//command_line_option:platforms": platforms, "//command_line_option:ios_minimum_os": _min_os_version_or_none( minimum_os_version = minimum_os_version, platform = "ios", @@ -272,6 +276,11 @@ _apple_rule_base_transition_inputs = _apple_rule_common_transition_inputs + [ "//command_line_option:tvos_cpus", "//command_line_option:watchos_cpus", ] +_apple_platform_transition_inputs = _apple_rule_base_transition_inputs + [ + "//command_line_option:apple_platforms", + "//command_line_option:incompatible_enable_apple_toolchain_resolution", + "//command_line_option:platforms", +] _apple_rule_base_transition_outputs = [ "//command_line_option:apple configuration distinguisher", "//command_line_option:apple_platform_type", @@ -283,6 +292,7 @@ _apple_rule_base_transition_outputs = [ "//command_line_option:grte_top", "//command_line_option:ios_minimum_os", "//command_line_option:macos_minimum_os", + "//command_line_option:platforms", "//command_line_option:tvos_minimum_os", "//command_line_option:watchos_minimum_os", "@build_bazel_rules_swift//swift:emit_swiftinterface", @@ -375,6 +385,34 @@ _static_framework_transition = transition( ], ) +def _apple_platform_transition_impl(settings, attr): + """Rule transition to handle setting crosstool and platform flags for Apple rules.""" + if settings["//command_line_option:incompatible_enable_apple_toolchain_resolution"]: + platforms = ( + settings["//command_line_option:apple_platforms"] or + settings["//command_line_option:platforms"] + ) + return _command_line_options( + minimum_os_version = attr.minimum_os_version, + platform_type = attr.platform_type, + platforms = platforms, + settings = settings, + ) + + # Ensure platforms aren't set so that platform mapping can take place. + return _command_line_options( + minimum_os_version = attr.minimum_os_version, + platform_type = attr.platform_type, + platforms = [], + settings = settings, + ) + +_apple_platform_transition = transition( + implementation = _apple_platform_transition_impl, + inputs = _apple_platform_transition_inputs, + outputs = _apple_rule_base_transition_outputs, +) + # TODO(b/230527536): Add support for Bazel platforms on ios/tvos_static_framework transition support method def _apple_common_multi_arch_split_key(*, cpu, environment, platform_type): """Returns split key for the apple_common.multi_arch_split transition based on target triplet. @@ -429,6 +467,7 @@ _xcframework_transition = transition( ) transition_support = struct( + apple_platform_transition = _apple_platform_transition, apple_rule_transition = _apple_rule_base_transition, apple_rule_arm64_as_arm64e_transition = _apple_rule_arm64_as_arm64e_transition, apple_universal_binary_rule_transition = _apple_universal_binary_rule_transition, diff --git a/test/starlark_tests/BUILD b/test/starlark_tests/BUILD index bfb60a44e5..6ecfc4a53d 100644 --- a/test/starlark_tests/BUILD +++ b/test/starlark_tests/BUILD @@ -1,6 +1,7 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load(":apple_bundle_version_tests.bzl", "apple_bundle_version_test_suite") load(":apple_core_data_model_tests.bzl", "apple_core_data_model_test_suite") +load(":apple_static_library_tests.bzl", "apple_static_library_test_suite") load(":apple_static_xcframework_tests.bzl", "apple_static_xcframework_test_suite") load(":apple_universal_binary_tests.bzl", "apple_universal_binary_test_suite") load(":apple_xcframework_import_tests.bzl", "apple_xcframework_import_test_suite") @@ -48,6 +49,8 @@ apple_bundle_version_test_suite(name = "apple_bundle_version") apple_core_data_model_test_suite(name = "apple_core_data_model") +apple_static_library_test_suite(name = "apple_static_library") + apple_static_xcframework_test_suite(name = "apple_static_xcframework") apple_universal_binary_test_suite(name = "apple_universal_binary") diff --git a/test/starlark_tests/apple_static_library_tests.bzl b/test/starlark_tests/apple_static_library_tests.bzl new file mode 100644 index 0000000000..5e69548f7c --- /dev/null +++ b/test/starlark_tests/apple_static_library_tests.bzl @@ -0,0 +1,254 @@ +# Copyright 2022 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. + +"""apple_static_library Starlark tests.""" + +load( + ":rules/analysis_lipo_test.bzl", + "analysis_lipo_test", +) +load( + ":rules/analysis_runfiles_test.bzl", + "analysis_runfiles_test", +) +load( + ":rules/analysis_symlink_test.bzl", + "analysis_symlink_test", +) +load( + ":rules/analysis_target_outputs_test.bzl", + "analysis_target_outputs_test", +) +load( + ":rules/common_verification_tests.bzl", + "binary_contents_test", +) + +def apple_static_library_test_suite(name): + """Test suite for apple_static_library. + + Args: + name: The base name to be used in things created by this macro. + """ + + # Test that the output library follows a given form of {target name}_lipo.a. + analysis_target_outputs_test( + name = "{}_output_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + expected_outputs = ["example_library_lipo.a"], + tags = [name], + ) + + # Test that the static library output generates a symlink action as one of its output actions + # for single arch builds. + analysis_symlink_test( + name = "{}_ios_symlink_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + tags = [name], + ) + + # Test that the static library output generates a lipo action as one of its output actions for + # multi arch iOS Simulator builds. + analysis_lipo_test( + name = "{}_ios_lipo_test".format(name), + expected_sdk_platform = "iPhoneSimulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + tags = [name], + ) + + # Test that the static library output generates a lipo action as one of its output actions for + # multi arch watchOS builds. + analysis_lipo_test( + name = "{}_watchos_lipo_test".format(name), + expected_sdk_platform = "WatchOS", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_watch_library_os8", + tags = [name], + ) + + # Test that the output library archive is added to the final list of runfiles, as well as any + # other library archive files that could be required at runtime execution. + analysis_runfiles_test( + name = "{}_runfiles_test".format(name), + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + expected_runfiles = [ + "third_party/bazel_rules/rules_apple/test/starlark_tests/targets_under_test/apple/static_library/example_library_lipo.a", + "third_party/bazel_rules/rules_apple/test/starlark_tests/targets_under_test/apple/static_library/libmain_lib.a", + ], + tags = [name], + ) + + # Verify that this is a "static library" as identified by macOS, which is also known as an + # archive produced by `ar`. + binary_contents_test( + name = "{}_file_info_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + binary_test_file = "$BINARY", + binary_contains_file_info = ["current ar archive"], + tags = [name], + ) + + # Test the output binary for minimum OS 8.0, using the old-style load commands that are no + # longer in binaries built for min OS iOS 14+ which don't explicitly distinguish the simulator. + binary_contents_test( + name = "{}_ios_binary_contents_intel_simulator_os8_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library", + cpus = { + "ios_multi_cpus": ["x86_64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + macho_load_commands_contain = ["cmd LC_VERSION_MIN_IPHONEOS", "version 8.0"], + tags = [name], + ) + + # The LC_BUILD_VERSION is always present in binaries built with minimum version >= 2020 Apple + # OSes, which will have fields of "minos {version number}" and "platform {platform enum}". + + # Test that the output binary is identified as iOS simulator (PLATFORM_IOSSIMULATOR) via the + # Mach-O load command LC_BUILD_VERSION for an Intel binary. + binary_contents_test( + name = "{}_ios_binary_contents_intel_simulator_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_os14", + cpus = { + "ios_multi_cpus": ["x86_64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 14.0", "platform IOSSIMULATOR"], + tags = [name], + ) + + # Test that the output binary is identified as iOS simulator (PLATFORM_IOSSIMULATOR) via the + # Mach-O load command LC_BUILD_VERSION for an Arm binary. + binary_contents_test( + name = "{}_ios_binary_contents_arm_simulator_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_os14", + cpus = { + "ios_multi_cpus": ["sim_arm64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "arm64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 14.0", "platform IOSSIMULATOR"], + tags = [name], + ) + + # Test that the output binary is identified as iOS device (PLATFORM_IOS) via the Mach-O load + # command LC_BUILD_VERSION for an Arm binary. + binary_contents_test( + name = "{}_ios_binary_contents_device_platform_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_os14", + cpus = { + "ios_multi_cpus": ["arm64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "arm64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 14.0", "platform IOS"], + tags = [name], + ) + + # Test that the output multi-arch binary is identified as iOS simulator (PLATFORM_IOSSIMULATOR) + # via the Mach-O load command LC_BUILD_VERSION for the Intel binary slice. + binary_contents_test( + name = "{}_ios_simulator_multiarch_intel_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_os14", + cpus = { + "ios_multi_cpus": ["x86_64", "sim_arm64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 14.0", "platform IOSSIMULATOR"], + tags = [name], + ) + + # Test that the output multi-arch binary is identified as iOS simulator (PLATFORM_IOSSIMULATOR) + # via the Mach-O load command LC_BUILD_VERSION for the Arm binary slice. + binary_contents_test( + name = "{}_ios_simulator_multiarch_arm_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_os14", + cpus = { + "ios_multi_cpus": ["x86_64", "sim_arm64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "arm64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 14.0", "platform IOSSIMULATOR"], + tags = [name], + ) + + # Test that the output binary is identified as watchOS simulator (PLATFORM_WATCHOSSIMULATOR) via + # the Mach-O load command LC_BUILD_VERSION for an Intel binary. + binary_contents_test( + name = "{}_watchos_binary_contents_intel_simulator_platform_test".format(name), + build_type = "simulator", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_watch_library_os8", + cpus = { + "watchos_cpus": ["x86_64"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 8.0", "platform WATCHOSSIMULATOR"], + tags = [name], + ) + + # Test that the output binary is identified as watchOS device (PLATFORM_WATCHOS) via the Mach-O + # load command LC_BUILD_VERSION for an Arm 64-on-32 binary. + binary_contents_test( + name = "{}_watchos_binary_contents_device_platform_test".format(name), + build_type = "device", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_watch_library_os8", + cpus = { + "watchos_cpus": ["arm64_32"], + }, + binary_test_file = "$BINARY", + binary_test_architecture = "arm64_32", + macho_load_commands_contain = ["cmd LC_BUILD_VERSION", "minos 8.0", "platform WATCHOS"], + tags = [name], + ) + + # Test that avoid_deps works on an apple_static_library target with objc_library deps. + binary_contents_test( + name = "{}_ios_avoid_deps_test".format(name), + build_type = "simulator", + compilation_mode = "opt", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_with_avoid_deps", + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + binary_contains_symbols = ["_doStuff"], + binary_not_contains_symbols = ["_frameworkDependent"], + tags = [name], + ) + + # Test that avoid_deps works on an apple_static_library target with cc_library deps. + binary_contents_test( + name = "{}_ios_cc_avoid_deps_test".format(name), + build_type = "simulator", + compilation_mode = "opt", + target_under_test = "//test/starlark_tests/targets_under_test/apple/static_library:example_library_with_cc_avoid_deps", + binary_test_file = "$BINARY", + binary_test_architecture = "x86_64", + binary_contains_symbols = ["_doStuff"], + binary_not_contains_symbols = ["_frameworkDependent"], + tags = [name], + ) + + native.test_suite( + name = name, + tags = [name], + ) diff --git a/test/starlark_tests/apple_universal_binary_tests.bzl b/test/starlark_tests/apple_universal_binary_tests.bzl index 75b4a16e7a..e37a23fd50 100644 --- a/test/starlark_tests/apple_universal_binary_tests.bzl +++ b/test/starlark_tests/apple_universal_binary_tests.bzl @@ -39,7 +39,7 @@ def apple_universal_binary_test_suite(name): binary_contents_test( name = "{}_x86_binary_contents_test".format(name), build_type = "device", - macos_cpus = ["x86_64", "arm64"], + cpus = {"macos_cpus": ["x86_64", "arm64"]}, target_under_test = "//test/starlark_tests/targets_under_test/apple:multi_arch_cc_binary", binary_test_file = "$BINARY", binary_test_architecture = "x86_64", @@ -51,7 +51,7 @@ def apple_universal_binary_test_suite(name): binary_contents_test( name = "{}_arm64_binary_contents_test".format(name), build_type = "device", - macos_cpus = ["x86_64", "arm64"], + cpus = {"macos_cpus": ["x86_64", "arm64"]}, target_under_test = "//test/starlark_tests/targets_under_test/apple:multi_arch_cc_binary", binary_test_file = "$BINARY", binary_test_architecture = "arm64", @@ -67,7 +67,7 @@ def apple_universal_binary_test_suite(name): binary_contents_test( name = "{}_forced_cpus_x86_binary_contents_test".format(name), build_type = "device", - macos_cpus = ["arm64"], + cpus = {"macos_cpus": ["arm64"]}, target_under_test = "//test/starlark_tests/targets_under_test/apple:multi_arch_forced_cpus_cc_binary", binary_test_file = "$BINARY", binary_test_architecture = "x86_64", @@ -79,7 +79,7 @@ def apple_universal_binary_test_suite(name): binary_contents_test( name = "{}_forced_cpus_arm64_binary_contents_test".format(name), build_type = "device", - macos_cpus = ["x86_64"], + cpus = {"macos_cpus": ["x86_64"]}, target_under_test = "//test/starlark_tests/targets_under_test/apple:multi_arch_forced_cpus_cc_binary", binary_test_file = "$BINARY", binary_test_architecture = "arm64", diff --git a/test/starlark_tests/rules/analysis_lipo_test.bzl b/test/starlark_tests/rules/analysis_lipo_test.bzl new file mode 100644 index 0000000000..bab478d47d --- /dev/null +++ b/test/starlark_tests/rules/analysis_lipo_test.bzl @@ -0,0 +1,68 @@ +# Copyright 2022 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. + +"Starlark test for verifying targets that generate actions related to lipo." + +load( + "@bazel_skylib//lib:unittest.bzl", + "analysistest", + "unittest", +) + +def _analysis_lipo_test_impl(ctx): + "Test the `lipo` action exists with expected environment variables." + env = analysistest.begin(ctx) + expected_env = { + "APPLE_SDK_PLATFORM": ctx.attr.expected_sdk_platform, + } + + no_lipo = True + for action in analysistest.target_actions(env): + if hasattr(action, "argv") and action.argv: + concat_action_argv = " ".join(action.argv) + if not "lipo " in concat_action_argv: + continue + for test_env_key in expected_env.keys(): + if not test_env_key in action.env: + unittest.fail(env, "\"{}\" not in lipo's environment, instead found: \"{}\".".format( + test_env_key, + " ".join(action.env.keys()), + )) + if action.env[test_env_key] != expected_env[test_env_key]: + unittest.fail(env, "\"{}\" did not match expected value \"{}\" for lipo's environment variable given key of \"{}\".".format( + action.env[test_env_key], + expected_env[test_env_key], + test_env_key, + )) + no_lipo = False + + if no_lipo: + unittest.fail(env, "Did not find any lipo actions to test.") + return analysistest.end(env) + +analysis_lipo_test = analysistest.make( + _analysis_lipo_test_impl, + attrs = { + "expected_sdk_platform": attr.string( + mandatory = True, + doc = "The expected Apple SDK platform that lipo will be called under.", + ), + }, + config_settings = { + "//command_line_option:macos_cpus": "arm64,x86_64", + "//command_line_option:ios_multi_cpus": "sim_arm64,x86_64", + "//command_line_option:tvos_cpus": "sim_arm64,x86_64", + "//command_line_option:watchos_cpus": "arm64_32,armv7k", + }, +) diff --git a/test/starlark_tests/rules/analysis_runfiles_test.bzl b/test/starlark_tests/rules/analysis_runfiles_test.bzl new file mode 100644 index 0000000000..acf59b2aa0 --- /dev/null +++ b/test/starlark_tests/rules/analysis_runfiles_test.bzl @@ -0,0 +1,46 @@ +# Copyright 2022 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. + +"Starlark test for verifying that the runfiles of targets are properly set." + +load( + "@bazel_skylib//lib:unittest.bzl", + "analysistest", + "unittest", +) + +def _analysis_runfiles_test_impl(ctx): + "Test that runfiles of the given target under test is properly set." + env = analysistest.begin(ctx) + + file_list = [x.short_path for x in analysistest.target_under_test(env)[DefaultInfo].data_runfiles.files.to_list()] + + for expected_runfile in ctx.attr.expected_runfiles: + if expected_runfile not in file_list: + unittest.fail(env, "\"{}\" not in target's runfiles, instead found:\n\"{}\".".format( + expected_runfile, + "\n".join(file_list), + )) + + return analysistest.end(env) + +analysis_runfiles_test = analysistest.make( + _analysis_runfiles_test_impl, + attrs = { + "expected_runfiles": attr.string_list( + mandatory = True, + doc = "A list of runfiles expected to be found in the given target.", + ), + }, +) diff --git a/test/starlark_tests/rules/analysis_symlink_test.bzl b/test/starlark_tests/rules/analysis_symlink_test.bzl new file mode 100644 index 0000000000..3d18f2390b --- /dev/null +++ b/test/starlark_tests/rules/analysis_symlink_test.bzl @@ -0,0 +1,48 @@ +# Copyright 2022 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. + +"Starlark test for verifying targets that generate actions related to symlinks." + +load( + "@bazel_skylib//lib:unittest.bzl", + "analysistest", + "unittest", +) + +def _analysis_symlink_test_impl(ctx): + "Test that a `lipo` action does not exist, and the target is symlinked." + env = analysistest.begin(ctx) + + no_symlink = True + for action in analysistest.target_actions(env): + if hasattr(action, "argv") and action.argv: + concat_action_argv = " ".join(action.argv) + if "lipo " in concat_action_argv: + unittest.fail(env, "An unexpected lipo action was found.") + elif action.mnemonic == "Symlink": + no_symlink = False + + if no_symlink: + unittest.fail(env, "Did not find any symlink actions to test.") + return analysistest.end(env) + +analysis_symlink_test = analysistest.make( + _analysis_symlink_test_impl, + config_settings = { + "//command_line_option:macos_cpus": "x86_64", + "//command_line_option:ios_multi_cpus": "x86_64", + "//command_line_option:tvos_cpus": "x86_64", + "//command_line_option:watchos_cpus": "x86_64", + }, +) diff --git a/test/starlark_tests/rules/apple_verification_test.bzl b/test/starlark_tests/rules/apple_verification_test.bzl index 59ab33a277..1ba2087888 100644 --- a/test/starlark_tests/rules/apple_verification_test.bzl +++ b/test/starlark_tests/rules/apple_verification_test.bzl @@ -19,7 +19,7 @@ that may change at any time. Please do not depend on this rule. """ load( - "@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", + "@build_bazel_rules_apple//apple/internal:apple_product_type.bzl", # buildifier: disable=bzl-visibility "apple_product_type", ) # buildifier: disable=bzl-visibility load( @@ -39,15 +39,9 @@ load( def _apple_verification_transition_impl(settings, attr): """Implementation of the apple_verification_transition transition.""" - # This was added because this transition is also used by - # `infoplist_contents_test` and has no "macos_cpus" attribute. - macos_cpus = "x86_64" - if hasattr(attr, "macos_cpus"): - macos_cpus = ",".join(attr.macos_cpus) - output_dictionary = { "//command_line_option:ios_signing_cert_name": "-", - "//command_line_option:macos_cpus": macos_cpus, + "//command_line_option:macos_cpus": "x86_64", "//command_line_option:compilation_mode": attr.compilation_mode, "//command_line_option:apple_bitcode": attr.apple_bitcode, "//command_line_option:apple_generate_dsym": attr.apple_generate_dsym, @@ -64,12 +58,19 @@ def _apple_verification_transition_impl(settings, attr): "//command_line_option:tvos_cpus": "arm64", "//command_line_option:watchos_cpus": "arm64_32,armv7k", }) + + if hasattr(attr, "cpus"): + for cpu_option, cpus in attr.cpus.items(): + command_line_option = "//command_line_option:%s" % cpu_option + output_dictionary.update({command_line_option: ",".join(cpus)}) + existing_features = settings.get("//command_line_option:features") or [] if hasattr(attr, "target_features"): existing_features.extend(attr.target_features) if hasattr(attr, "sanitizer") and attr.sanitizer != "none": existing_features.append(attr.sanitizer) output_dictionary["//command_line_option:features"] = existing_features + return output_dictionary apple_verification_transition = transition( @@ -231,10 +232,10 @@ https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode If true, generates .dSYM debug symbol bundles for the target(s) under test. """, ), - "macos_cpus": attr.string_list( + "cpus": attr.string_list_dict( doc = """ -List of MacOS CPU's to use for test under target. -https://docs.bazel.build/versions/main/command-line-reference.html#flag--macos_cpus +Dictionary of command line options cpu flags (e.g. ios_multi_cpus, macos_cpus) and the list of +cpu's to use for test under target (e.g. {'ios_multi_cpus': ['arm64', 'x86_64']}) """, ), "sanitizer": attr.string( diff --git a/test/starlark_tests/rules/common_verification_tests.bzl b/test/starlark_tests/rules/common_verification_tests.bzl index 4d75d6ea35..3725f7f04e 100644 --- a/test/starlark_tests/rules/common_verification_tests.bzl +++ b/test/starlark_tests/rules/common_verification_tests.bzl @@ -187,6 +187,9 @@ def binary_contents_test( binary_test_architecture = "", binary_contains_symbols = [], binary_not_contains_symbols = [], + binary_contains_file_info = [], + macho_load_commands_contain = [], + macho_load_commands_not_contain = [], embedded_plist_test_values = {}, plist_section_name = "__info_plist", **kwargs): @@ -203,6 +206,12 @@ def binary_contents_test( specified in `binary_test_file`. binary_not_contains_symbols: Optional, A list of symbols that should not appear in the binary file specified in `binary_test_file`. + binary_contains_file_info: Optional, A list of strings that should appear as substrings of + the output when the binary is queried by the `file` command. + macho_load_commands_contain: Optional, A list of Mach-O load commands that should appear in + the binary file specified in `binary_test_file`. + macho_load_commands_not_contain: Optional, A list of Mach-O load commands that should not + appear in the binary file specified in `binary_test_file`. embedded_plist_test_values: Optional, The key/value pairs to test. The test will fail if the key does not exist or if its value doesn't match the specified value. * can be used as a wildcard value. An embedded plist will be extracted from the @@ -212,11 +221,19 @@ def binary_contents_test( Defaults to `__info_plist`. **kwargs: Other arguments are passed through to the apple_verification_test rule. """ - if any([binary_contains_symbols, binary_not_contains_symbols]) and not binary_test_architecture: + if any([binary_contains_symbols, binary_not_contains_symbols]) and ( + not binary_test_architecture + ): fail("Need binary_test_architecture when checking symbols") - elif binary_test_architecture and not any([binary_contains_symbols, binary_not_contains_symbols]): - fail("Need binary_contains_symbols and/or binary_not_contains_symbols when checking an " + - "architecture") + elif binary_test_architecture and not any([ + binary_contains_symbols, + binary_not_contains_symbols, + macho_load_commands_contain, + macho_load_commands_not_contain, + ]): + fail("Need at least one of (binary_contains_symbols, binary_not_contains_symbols, " + + "macho_load_commands_contain, macho_load_commands_not_contain) when specifying " + + "binary_test_architecture") if not any([binary_test_file, embedded_plist_test_values]): fail("There are no tests for the binary") @@ -236,6 +253,9 @@ def binary_contents_test( "BINARY_TEST_ARCHITECTURE": [binary_test_architecture], "BINARY_CONTAINS_SYMBOLS": binary_contains_symbols, "BINARY_NOT_CONTAINS_SYMBOLS": binary_not_contains_symbols, + "BINARY_CONTAINS_FILE_INFO": binary_contains_file_info, + "MACHO_LOAD_COMMANDS_CONTAIN": macho_load_commands_contain, + "MACHO_LOAD_COMMANDS_NOT_CONTAIN": macho_load_commands_not_contain, "PLIST_SECTION_NAME": [plist_section_name], "PLIST_TEST_VALUES": plist_test_values_list, }, diff --git a/test/starlark_tests/targets_under_test/apple/BUILD b/test/starlark_tests/targets_under_test/apple/BUILD index f75a2a058b..863539f68e 100644 --- a/test/starlark_tests/targets_under_test/apple/BUILD +++ b/test/starlark_tests/targets_under_test/apple/BUILD @@ -998,6 +998,12 @@ genrule( cmd = "echo '#import \nvoid doStuff() { NSLog(@\"Framework method called\"); }' > $@", ) +genrule( + name = "dummy_fmwk_cc_src", + outs = ["DummyFmwk.c"], + cmd = "echo '#include \nvoid doStuff() { printf(\"Framework method called\\n\"); }' > $@", +) + genrule( name = "dummy_fmwk_swift_src", outs = ["DummyFmwk.swift"], @@ -1010,6 +1016,12 @@ genrule( cmd = "echo '#import \nvoid frameworkDependent() { NSLog(@\"frameworkDependent() called\"); }' > $@", ) +genrule( + name = "dummy_fmwk_dependent_cc_src", + outs = ["DummyFmwkDependent.c"], + cmd = "echo '#include \nvoid frameworkDependent() { printf(\"frameworkDependent() called\\n\"); }' > $@", +) + objc_library( name = "StaticFmwkUpperLib", srcs = [ @@ -1020,12 +1032,28 @@ objc_library( deps = [":StaticFmwkLowerLib"], ) +objc_library( + name = "StaticFmwkCcUpperLib", + srcs = [ + "DummyFmwk.c", + "DummyFmwk.h", + ], + tags = TARGETS_UNDER_TEST_TAGS, + deps = [":StaticFmwkCcLowerLib"], +) + objc_library( name = "StaticFmwkLowerLib", srcs = ["DummyFmwkDependent.m"], tags = TARGETS_UNDER_TEST_TAGS, ) +cc_library( + name = "StaticFmwkCcLowerLib", + srcs = ["DummyFmwkDependent.c"], + tags = TARGETS_UNDER_TEST_TAGS, +) + swift_library( name = "SwiftFmwkWithGenHeader", srcs = ["DummyFmwk.swift"], diff --git a/test/starlark_tests/targets_under_test/apple/static_library/BUILD b/test/starlark_tests/targets_under_test/apple/static_library/BUILD new file mode 100644 index 0000000000..827a70cd18 --- /dev/null +++ b/test/starlark_tests/targets_under_test/apple/static_library/BUILD @@ -0,0 +1,66 @@ +load( + "//apple:apple_static_library.bzl", + "apple_static_library", +) +load( + "//test/starlark_tests:common.bzl", + "FIXTURE_TAGS", +) + +licenses(["notice"]) + +package( + default_testonly = 1, + default_visibility = ["//test/starlark_tests:__subpackages__"], +) + +objc_library( + name = "main_lib", + srcs = ["@bazel_tools//tools/objc:dummy.c"], + tags = FIXTURE_TAGS, + deps = [ + "//test/starlark_tests/resources:objc_main_lib", + ], +) + +apple_static_library( + name = "example_library", + minimum_os_version = "8.0", + platform_type = "ios", + tags = FIXTURE_TAGS, + deps = [":main_lib"], +) + +apple_static_library( + name = "example_library_os14", + minimum_os_version = "14.0", + platform_type = "ios", + tags = FIXTURE_TAGS, + deps = [":main_lib"], +) + +apple_static_library( + name = "example_watch_library_os8", + minimum_os_version = "8.0", + platform_type = "watchos", + tags = FIXTURE_TAGS, + deps = [":main_lib"], +) + +apple_static_library( + name = "example_library_with_avoid_deps", + avoid_deps = ["//test/starlark_tests/targets_under_test/apple:StaticFmwkLowerLib"], + minimum_os_version = "8.0", + platform_type = "ios", + tags = FIXTURE_TAGS, + deps = ["//test/starlark_tests/targets_under_test/apple:StaticFmwkUpperLib"], +) + +apple_static_library( + name = "example_library_with_cc_avoid_deps", + avoid_deps = ["//test/starlark_tests/targets_under_test/apple:StaticFmwkCcLowerLib"], + minimum_os_version = "8.0", + platform_type = "ios", + tags = FIXTURE_TAGS, + deps = ["//test/starlark_tests/targets_under_test/apple:StaticFmwkCcUpperLib"], +) diff --git a/test/starlark_tests/verifier_scripts/binary_contents_test.sh b/test/starlark_tests/verifier_scripts/binary_contents_test.sh index 695a5393fd..5d191dad4a 100644 --- a/test/starlark_tests/verifier_scripts/binary_contents_test.sh +++ b/test/starlark_tests/verifier_scripts/binary_contents_test.sh @@ -28,6 +28,12 @@ newline=$'\n' # `BINARY_CONTAINS_SYMBOLS`. # BINARY_CONTAINS_SYMBOLS: Array of symbols that should be present. # BINARY_NOT_CONTAINS_SYMBOLS: Array of symbols that should not be present. +# BINARY_CONTAINS_FILE_INFO: Array of strings that should be present as +# substrings of the reported `file` information. +# MACHO_LOAD_COMMANDS_CONTAIN: Array of Mach-O load commands that should +# be present. +# MACHO_LOAD_COMMANDS_NOT_CONTAIN: Array of Mach-O load commands that should +# not be present. # PLIST_SECTION_NAME: Name of the plist section to inspect values from. If not # supplied, will test the embedded Info.plist slice at __TEXT,__info_plist. # PLIST_TEST_VALUES: Array for keys and values in the format "KEY VALUE" where @@ -88,6 +94,63 @@ if [[ -n "${BINARY_TEST_FILE-}" ]]; then fi done fi + + if [[ -n "${MACHO_LOAD_COMMANDS_CONTAIN-}" || -n "${MACHO_LOAD_COMMANDS_NOT_CONTAIN-}" ]]; then + # The `otool` commands below remove the leftmost white space from the + # output to make string matching of symbols possible, avoiding the + # accidental elimination of white space from paths and identifiers. + IFS=$'\n' + if [[ -n "${BINARY_TEST_ARCHITECTURE-}" ]]; then + arch=$(eval echo "$BINARY_TEST_ARCHITECTURE") + if [[ ! -n $arch ]]; then + fail "No architecture specified for binary file at \"$path\"" + else + actual_symbols=($(otool -v -arch "$arch" -l "$path" | awk '{$1=$1}1')) + fi + else + actual_symbols=($(otool -v -l "$path" | awk '{$1=$1}1')) + fi + if [[ -n "${MACHO_LOAD_COMMANDS_CONTAIN-}" ]]; then + for test_symbol in "${MACHO_LOAD_COMMANDS_CONTAIN[@]}" + do + something_tested=true + symbol_found=false + for actual_symbol in "${actual_symbols[@]}" + do + if [[ "$actual_symbol" == "$test_symbol" ]]; then + symbol_found=true + break + fi + done + if [[ "$symbol_found" = false ]]; then + fail "Expected load command \"$test_symbol\" was not found." \ + "The load commands in the binary were:" \ + "$newline${actual_symbols[@]}" + fi + done + fi + + if [[ -n "${MACHO_LOAD_COMMANDS_NOT_CONTAIN-}" ]]; then + for test_symbol in "${MACHO_LOAD_COMMANDS_NOT_CONTAIN[@]}" + do + something_tested=true + symbol_found=false + for actual_symbol in "${actual_symbols[@]}" + do + if [[ "$actual_symbol" == "$test_symbol" ]]; then + symbol_found=true + break + fi + done + if [[ "$symbol_found" = true ]]; then + fail "Unexpected load command \"$test_symbol\" was found." \ + "The load commands in the binary were:" \ + "$newline${actual_symbols[@]}" + fi + done + fi + fi + else if [[ -n "${BINARY_CONTAINS_SYMBOLS-}" ]]; then fail "Rule Misconfigured: Supposed to look for symbols," \ @@ -97,6 +160,37 @@ if [[ -n "${BINARY_TEST_FILE-}" ]]; then fail "Rule Misconfigured: Supposed to look for missing symbols," \ "but no arch was set to check: ${BINARY_NOT_CONTAINS_SYMBOLS[@]}" fi + if [[ -n "${MACHO_LOAD_COMMANDS_CONTAIN-}" ]]; then + fail "Rule Misconfigured: Supposed to look for macho load commands," \ + "but no arch was set to check: ${MACHO_LOAD_COMMANDS_CONTAIN[@]}" + fi + if [[ -n "${MACHO_LOAD_COMMANDS_NOT_CONTAIN-}" ]]; then + fail "Rule Misconfigured: Supposed to look for missing macho load commands," \ + "but no arch was set to check: ${MACHO_LOAD_COMMANDS_NOT_CONTAIN[@]}" + fi + fi + + # Use `file --brief` to verify how the file is recognized by macOS, and other + # Apple platforms by proxy. + if [[ -n "${BINARY_CONTAINS_FILE_INFO-}" ]]; then + IFS=$'\n' file_info_output=($(file --brief "$path" | awk '{$1=$1}1')) + for file_info_test_substring in "${BINARY_CONTAINS_FILE_INFO[@]}" + do + something_tested=true + output_found=false + for file_info_line in "${file_info_output[@]}" + do + if [[ "$file_info_line" == *"$file_info_test_substring"* ]]; then + output_found=true + break + fi + done + if [[ "$output_found" = false ]]; then + fail "Expected file output \"$file_info_test_substring\" was not " \ + "found. The file output for the binary was:" \ + "$newline${file_info_output[@]}" + fi + done fi # Use `launchctl plist` to test for key/value pairs in an embedded plist file.